-
-
Notifications
You must be signed in to change notification settings - Fork 570
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
How is jotai different from zustand? #13
Comments
Jotai looks fun to play with, thank you! I've just realized that Zustand and Jotai were made by you guys @ react-spring, this brings the question, what kind of problems have you run into that justified the development of two different (and possibly concurrent) state management libraries? Is there any concerns of one library being cannibalized by the other one or splitting efforts? |
While we were discussing about improving Zustand, we had two major points that we can't implement in Zustand. The first is that Zustand is an external store by design. We want a lib that only store states internally in React, which we believe supports CM better. The second is atom model that is often requested in some Zustand issues. We can't simply add the different concept into Zustand without influencing the feature complete API. So, we decided to release another lib from the same org. Each has different goal and concept. Both are more or less unopinionated and let users choose. |
Thanks for awesome library!!
What does it mean that ”extenal store” and ”store states internally in React ”? |
@sonatard "Internal state" means |
I was curious about the statement:
So I made a lib which looks/feels like jotai/recoil but the atoms are outside the tree: https://github.com/merisbahti/klyva Take a look at the tests: The atom exposes two functions: But also, for fun, I exposed a function Thanks again for this library, it’s very inspiring. ps. const useAtom = <T>(atom: Atom<T>) => {
const [value, setValue] = React.useState(atom.getValue())
React.useEffect(() => atom.subscribe(setValue), [atom])
return [atom.getValue, atom.update]
} |
@merisbahti Hi! Glad to see you got inspired and created something. btw, you might be interested in this issue and the discussion: #44 |
aah I was wondering why that was the case, I see, well then that explains it for me! thanks! btw I'm the author for that linked issue ;) |
aaah, what a shame. didn't pay much attention. (I remember your profile picture, thought it's on Twitter, looked for it...) |
I need the same article, but for Recoil. |
You mean the comparison between Jotai and Recoil? |
Yup. I saw small explanation in the README.md, but it's too short to decide. For me it sounds like "we don't use strings as a keys - that's the main reason to use Jotai". In the other words, if I'll make a simple wrapper for Recoil to exclude string keys - what's the difference will be then? |
Alright. First off, I'm familiar with Jotai and Zustand about their implementations, the backgrounds and goals. However, I'm not familiar with Recoil implementation at all. I grasp the Recoil docs, but don't understand it fully. Hence, it's pretty biased and maybe not accurate. Having that said, yes the biggest difference is no string keys. Recoil has it by design to allow serialization easily. Jotai avoid the string keys to simplify the API for use cases without serialization, and serialization is left for developers (we are trying to come up with best practice about serialization.) The API design is also different. While Recoil provides various functions to fulfill their requirements, Jotai's focus is to provide primitive APIs, and everything else is built on it. This will help learning the APIs in a leaner way, and creating another function based on primitives (encouraging ecosystem). The implementation should be very different, but let's not go in deep this time. Here's the summary. How is jotai different from recoil?Developer
Basis
Technical difference
When to use which
|
This kind of comparisons are great @dai-shi, I would add them to front page documentation. |
But they are rather too big to fit in the readme. What would you suggest otherwise? |
Yes, sorry, I meant adding links, agree. I'd keep front page tidy & simple, with quick access to the most relevant topics. I find these kind of comparisons extremely useful as long as they are based on technical facts, as you did (instead of marketing slogans as you can see in many other libraries). |
@dai-shi what does CM stand for? |
Concurrent Mode. Still experimental one. https://reactjs.org/docs/concurrent-mode-intro.html |
awesome explanation guys. I would be very nice to have a comparison between Zustand or Jotai and Redux. Question: what are the differences of having the state in react (jotai) over having it externally (zustand) ? I get that the zustand store can be accessed outside of the react world, but why have it in react ? |
Do you mean a comparison between jotai and redux? What kind of comparison do you expect?
zustand (and valtio) is module state, and it's basically global in JS memory. You can do something like this: const countAtom = atom(0)
const Counter = () => {
const [count, setCount] = useAtom(countAtom)
return <div>{count} <button onClick={() => setCount((c) => c + 1)}>+1</button></div>
}
const App = () => (
<>
<Provider>
<Counter />
<Counter />
</Provider>
<Provider>
<Counter />
<Counter />
</Provider>
</>
) jotai state is like |
Sorry, I mean a comparison between jotai, zustand and redux, It would be more of a comparison of speed, if one has quirks (zombie child issue for example), caveats, limitations.
Are there performance penalties or any other caveats of picking module state over react state ? |
Speed/performance is a hard one. If it's about extra re-renders in React, all work good. For implementation efficiency, one would need to measure in a similar setting to your app. There might be differences, but technically jotai/zustand/redux all depend on immutability, so theoretical limit should be similar. valtio depends on proxies (like mobx) and can have some overhead. Zombie child issue comes with selectors, so it's zustand and redux only. Suspense support (technically experimental) are in jotai and valtio only.
Recently, I got a simple conclusion about it. (Apart from concurrent mode familiarity, which we don't know yet.) (The same is true in useContext+useState approach. Using just one context provider in an app is almost like misusing React Context, technically speaking. This would change if |
@dai-shi What if one wants this?
|
"localized" and "everything outside" sound conflicting?? |
yeah, that’s how it sounds.. I meant only that it would be nice to co-locate state with components, but not have the stores themselves be inside/tied to the render tree (yet somehow be able to target and trigger rendering.. maybe hooks is the best for this after all…). So sort of like Zustand, if I’m not mistaken, but atomized design which is bottom-up composable. Maybe Jotai with hooks would be the best base for something like that, but with some centralized store allowing for time-travel debugging. |
If I'm not mistaken, time-traveling is still possible with internal states. |
@dai-shi - Can you explain a little more how Suspense is supported (or what that gives you) for Jotai + valtio, which is missing in zustand? We are assessing between zustand & valtio which is better for us, and suspense support is stated as a thing. What does suspense support achieve? (the stores would be 'in' the loaded module no?.. how does suspense affect them) |
Well, it will require a series of blog posts to cover all. const fetchData = async () => {
// ...
return 'hello'
} In zustand, the store can have an action like this. import create from 'zustand'
const useStore = create((set) => ({
data: '',
doFetch: async () => {
const data = await fetchData()
set({ data })
},
})) If we want to show a loading indicator, we need a property for it. const useStore = create((set) => ({
data: '',
isLoading: false,
doFetch: async () => {
set({ isLoading: true })
const data = await fetchData()
set({ data, isLoading: false })
},
})) And use the property in your component somehow. With valtio, on the other hand, it can be done like this: import { proxy } from 'valtio'
const state = proxy({
data: '',
doFetch: () => {
state.data = fetchData()
},
}) And, you use Jotai can do the same like valtio, and in addition, jotai supports async derived atoms which also suspend. |
I think there should be one additional point here as it's the reason I had to choose Zustand over Jotai:
Although I see #610 was updated so maybe there's a workaround in Jotai now? But in any case I think it's still not officially supported in Jotai. |
From React perspective, transient update is a hack, even with zustand, because it's essentially introduces inconsistency between state and view. It's unavoidable for game (or alike) developers though. btw, technically speaking you don't need zustand for transient update, just read from a global variable. (So, it's not a good selling point at least for me... but yeah zustand/valtio readme explicitly notes so.) |
You can use Suspense with Zustand in a few ways. Here's one that I use often - handle the async operation in a component instead of in the store. const useStore = create((set) => ({
data: '',
setData: (data) => set({ data }),
})) Then you create a component wrapped in Suspense to handle the loading and call the function LoadData {
const data = fetchDataAsync();
const setData = useStore((state) => state.setData);
useEffect(() => {
setData(data);
}, [data]);
return null; // or something else if you want
}
function App() {
return (
<Suspense fallback={<h1>Loading data...</h1>}>
<LoadData />
</Suspense>
);
} |
It works, but it's not ideal because useEffect is delayed. Suspense is essentially a method to avoid such a delay. |
For many uses, such as removing a loading screen, a one frame delay is not an issue. But there are other cases where would be a problem, I agree. Can you use |
Yeah makes sense. However for me, dealing with three.js, I often don't know what I need to treat transiently or not in advance - especially as something that starts as a simple component grows in complexity over time . With Zustand it's very quick to switch if I am finding slowdown caused by state updates in a component. I can just switch to a |
No, it's mostly the same thing.
From app developer perspective, I agree. Suspense is good even with fetch-on-render.
This makes me understand where you are from. |
OK I think I understand that. You still need to pass the updated state to the store and then wait for other components to receive it, and it doesn't matter if you do this at the start of the cycle (synchronously) with
I have not heard it stated this way before, that's interesting. I guess my suggestion to do the following negates some of the benefit of Suspense in that case? Although you still get the clean code style and you hide the implementation in the const data = fetchDataAsync();
const setData = useStore((state) => state.setData);
useEffect(() => {
setData(data);
}, [data]); |
There's only an old doc at https://17.reactjs.org/docs/concurrent-mode-suspense.html.
Yeah, better than before, but using You might be interested in some of my other projects. |
Thanks for the info, I will take a look at those :) |
Jotai provides a very easy way to create a truly reactive derived (computed) state. This process is seamless in Jotai and much easier than in Zustand. |
Name
Jotai means "state" in Japanese.
Zustand means "state" in German.
Analogy
Jotai is close to Recoil.
Zustand is close to Redux.
Where state resides
Jotai state is within React component tree.
Zustand state is in the store outside React.
How to structure state
Jotai state consists of atoms (i.e. bottom-up).
Zustand state is one object (i.e. top-down).
Technical difference
The major difference is the state model. Zustand is basically a single store (you could create multiple stores, but they are separated.) Jotai is primitive atoms and composing them. In this sense, it's the matter of programming mental model.
Jotai can be seen as a replacement for useState+useContext. Instead of creating multiple contexts, atoms share one big context.
Zustand is an external store and the hook is to connect the external world to the React world.
When to use which
If you need a global store to bridge between two react renderers, zustand will only work.(no longer valid with jotai's provider-less mode, but zustand may work better.)The text was updated successfully, but these errors were encountered: