Skip to content

docs: Setting up client global state #13000

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
sacrosanctic opened this issue Nov 14, 2024 · 10 comments
Open

docs: Setting up client global state #13000

sacrosanctic opened this issue Nov 14, 2024 · 10 comments
Labels
documentation Improvements or additions to documentation

Comments

@sacrosanctic
Copy link
Contributor

sacrosanctic commented Nov 14, 2024

Describe the bug

Setting up client global state is a footgun. (see #4339). Almost 3 years and users are still confused about how to do it safely. I want to add this pattern to the docs. This should also help clear up confusion around server shared state.

On top of that, it resolves optimistic UI issues mentioned in #12999.

To summarize, this pattern provides a state that can be updated from the server and the client, is in sync across child components, has type safety and is safe from data leaks.

// ReactiveState.svelte.js
export class ReactiveState {
    value = $state();

    constructor(initialValue) {
        this.value = initialValue
    }
}
// routes/+layout.js
export const load = () => {
	const randomNumber = Math.floor( Math.random() * 11)

	return { state: new ReactiveState(randomNumber) }
}
<script>
// +page.svelte
	let { data } = $props()
</script>

{data.state.value}

<button onclick={() => { data.state.value++ }}>add one</button>

repl: https://www.sveltelab.dev/90t6s7u8l5svxn0

Alternatives

There is an alternative which involves defining your own context.
https://discord.com/channels/457912077277855764/1303014718268637264
https://discord.com/channels/457912077277855764/1303116841895333918
https://discord.com/channels/457912077277855764/1301456686963359795

Severity

annoyance

Additional Information

Prior discussions

https://discord.com/channels/457912077277855764/1305894527738974258

Use cases

websocket updates
https://discord.com/channels/457912077277855764/1306898519184904235

persistent state across pages
https://discord.com/channels/457912077277855764/1307335664743747614
https://discord.com/channels/457912077277855764/1313490960524775435

when is it safe to use global state
https://discord.com/channels/457912077277855764/1313289238762225694

https://discord.com/channels/457912077277855764/1316016073576812554

@good-dev-student
Copy link

in event you can use event.parent() to get parent data

@ahkelly
Copy link

ahkelly commented Dec 30, 2024

Any further update on this implementation idea as this example does not work with server data? (change +layout.js to +layout.server.js and it is no longer reactive)

@sacrosanctic
Copy link
Contributor Author

sacrosanctic commented Dec 30, 2024

Please don't quote the whole post, it makes the thread hard to follow.

Server doesn't need reactivity. If you're looking for shared state on the server, use event.locals

@ahkelly
Copy link

ahkelly commented Dec 30, 2024

I am not referring to shared state on the server. I am referring to the data coming from the server being reactive client side as your example shows but only for client data. Basically return data.my_db_array from the server and make it reactive via a bind: in +page.svelte.

using your example, below blows up as non-serializable in current svelte 5 release

+page.server.js
<script>
export const load = () => {
	const randomNumber = Math.floor( Math.random() * 11)
	return { state: new ReactiveState(randomNumber) }
}
</script>

@sacrosanctic
Copy link
Contributor Author

sacrosanctic commented Dec 30, 2024

In that case, pass the data from +layout.server.ts to +layout.ts, then follow the example.

@ahkelly
Copy link

ahkelly commented Dec 30, 2024

Wow, that extremely elegant solution sums up the state of this problem :) I guess we are just basically back to $effect() workarounds for now. I appreciate your efforts on this.

@sacrosanctic
Copy link
Contributor Author

Nice that you got it to work. But I'm not sure what you mean by $effect workaround, this example doesn't use any $effect.

@ahkelly
Copy link

ahkelly commented Dec 30, 2024

Sorry for confusion on that. I was just trying to say in a nice way the gymnastics of passing data from +layout.server.ts to +layout.ts is untenable and instead I'll have to revert back to #12999 (3.2 option) which I also don't love but it's not as awkward as having to add hundreds of +page.ts everywhere that I currently only ever use +page.server.ts as all my apps have a db backend.

In short no I still have no 100% working solution for this after many, many attempts.

@sacrosanctic
Copy link
Contributor Author

sacrosanctic commented Dec 31, 2024

That makes sense, this is only worth it if you can use a +layout.

Another solution is to use the new transform feature. Then you can go directly from +page.server.ts to +page.svelte

@CaptainCodeman
Copy link
Contributor

I use this pattern a lot, here's a more real-world example with some additional complexity if it helps anyone, includes:

  • Setting initial state based on request + server data
  • Dependencies between different classes / state
  • Client-side persistence

https://github.com/CaptainCodeman/shopping-cart

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

5 participants