pmndrs / jotai Goto Github PK
View Code? Open in Web Editor NEW👻 Primitive and flexible state management for React
Home Page: https://jotai.org
License: MIT License
👻 Primitive and flexible state management for React
Home Page: https://jotai.org
License: MIT License
First of all, amazing library, thank you so much :)
When I initialize an atom
that will make an async call to an API endpoint, is there a way to pass an attribute? I know that I could write a primitive atom
that will hold the parameter and make the async one dependent on it, but a more direct approach would be more elegant in my opinion.
I tried initializing it as follows but it keeps re-rendering my component infinitely and thus also makes infinite calls to the API
const fetchData =(id) atom({
async ()=>{
const res = await getDataByID(id)
return res.data
}
})
Thanks for the support!
On which browsers will jotai work ?
Is IE11 supported ?
Here's a draft for API docs.
atom
is a function to create an atom config. It's an object and the object identity is important. You can create it from everywhere. Once created, you shouldn't modify the object. (Note: There might be an advanced use case to mutate atom configs after creation. At the moment, it's not officially supported though.)
const primitiveAtom = atom(initialValue)
const derivedAtomWithRead = atom(readFunction)
const derivedAtomWithReadWrite = atom(readFunction, writeFunction)
const derivedAtomWithWriteOnly = atom(null, writeFunction)
There are two kinds of atoms: a writable atom and a read-only atom
Primitive atoms are always writable. Derived atoms are writable if writeFunction is specified.
The writeFunction of primitive atoms is equivalent to the setState of React.useState.
The signature of readFunction is (get) => Value | Promise<Value>
, and get
is a function that takes an atom config and returns its value stored in Provider described below.
Dependency is tracked, so if get
is used for an atom at least once, then whenever the atom value is changed, the readFunction will be reevaluated.
The signature of writeFunction is (get, set, update) => void | Promise<void>
.
get
is similar to the one described above, but it doesn't track the dependency. set
is a function that takes an atom config and a new value, and update the atom value in Provider. update
is an arbitrary value that we receive from the updating function returned by useAtom described below.
Atom configs don't hold values. Atom values are stored in a Provider. A Provider can be used like React context provider. Usually, we place one Provider at the root of the app, however you could use multiple Providers, each storing different atom values for its component tree.
const Root = () => (
<Provider>
<App />
</Provider>
)
The useAtom hook is to read an atom value stored in the Provider. It returns the atom value and an updating function as a tuple, just like useState. It takes an atom config created with atom()
. Initially, there is no value stored in the Provider. At the first time the atom is used via useAtom
, it will add an initial value in the Provider. If the atom is a derived atom, the read function is executed to compute an initial value. When an atom is no longer used, meaning the component using it is unmounted, the value is removed from the Provider.
const [value, updateValue] = useAtom(anAtom)
The updateValue
takes just one argument, which will be passed to the third argument of writeFunction of the atom. The behavior is totally depends on how the writeFunction is implemented.
Atoms can be created at anytime, like event callbacks and useEffect, or in promises.
Here's a simple Todo example.
import * as React from "react";
import { useState, FormEvent } from "react";
import { Provider, atom, useAtom, PrimitiveAtom } from "jotai";
type Todo = {
title: string;
completed: boolean;
};
const TodoItem: React.FC<{
todoAtom: PrimitiveAtom<Todo>;
remove: () => void;
}> = ({ todoAtom, remove }) => {
const [item, setItem] = useAtom(todoAtom);
const toggleCompleted = () => {
setItem((prev) => ({ ...prev, completed: !prev.completed }));
};
return (
<li>
<label>
<input
type="checkbox"
checked={item.completed}
onChange={toggleCompleted}
/>
<span style={{ textDecoration: item.completed ? "line-through" : "" }}>
{item.title}
</span>
{item.completed && <button onClick={remove}>Remove</button>}
</label>
</li>
);
};
const todosAtom = atom<PrimitiveAtom<Todo>[]>([]);
const TodoList: React.FC = () => {
const [title, setTitle] = useState("");
const [todos, setTodos] = useAtom(todosAtom);
const addTodo = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const todoAtom = atom<Todo>({ title, completed: false });
setTodos((prev) => [...prev, todoAtom]);
setTitle("");
};
const removeTodo = (todoAtom: PrimitiveAtom<Todo>) => {
setTodos((prev) => prev.filter((item) => item !== todoAtom));
};
return (
<ul>
{todos.map((todoAtom) => (
<TodoItem
key={todoAtom.key}
todoAtom={todoAtom}
remove={() => removeTodo(todoAtom)}
/>
))}
<li>
<form onSubmit={addTodo}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter title..."
/>
</form>
</li>
</ul>
);
};
const App: React.FC = () => (
<Provider>
<TodoList />
</Provider>
);
export default App;
Using Next.js
Attached screenshot
The code is like this
import { atom, useAtom } from 'jotai'
import { useUpdateAtom } from 'jotai/utils'
const eventsAtom = atom<Events>([])
export const useCreateSSE = () => {
let updateEvents = useUpdateAtom(eventsAtom)
}
Wrapping Component in _app with Provider
And then using this custom hook in some component
When I used useUpdateAtom code directly, just copied from the repo, it worked.
my tsconfig:
{
"compilerOptions": {
"target": "es6",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"downlevelIteration": true,
"baseUrl": ".",
"allowSyntheticDefaultImports": true,
"jsx": "preserve",
"paths": {
"~/*": ["./src/*"]
}
},
"exclude": ["node_modules", "public", "not_used"],
"include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", "src/**/*.jsx", "src/**/*.js"]
}
Currently, setters of derived atoms do not support functional updates.
Proposed API:
const listAtom = atom([]);
const sortedListAtom = atom((get) => get(listAtom), (_get, set, update) => set(listAtom, update));
const App = () => {
const [list, setList] = useAtom(sortedListAtom);
return <button onClick={() => setList((prev) => [...prev, "oi!"])}>add</button>
};
In other words, is there a reason why the setter of derived atom isn't a SetStateAction
same as for primitive atoms?
https://github.com/pmndrs/jotai/blob/master/docs/showcase.md has only a few examples with no description.
The dispatch
function of reducer atoms should support optional updates if the reducer defines the update as optional.
To detect this, Jotai could use the same pattern that React's useReducer
does:
type ReducerWithoutAction<V> = (value: V) => V;
const reducerWithOptionalAction: ReducerWithoutAction<number> = (value, action?: boolean) => v;
Use case:
const sidebarAtom = atomWithReducer(true, (prev, update?: boolean) => {
return update == null ? !prev : update
});
export default function App() {
const [isSidebarVisible, toggleSidebar] = useAtom(sidebarAtom);
return <button onClick={() => toggleSidebar()}>toggle</button>;
}
My main goal here is to discuss about it if there's no core functionality or util that does it.
Recoil has atomFamily which allows atoms to be created and referenced with something akin to a groupId. It seems like that kind of behavior can not be replicated without some kind string key identifiers.
After #107, or in parallel with it, we would want to create a benchmark tool for immutableMap.
It would allow us to evaluate the current implementation, compare possible alternative implementations, and improve the entire performance of jotai.
Anybody keen about performance?
We don't have any tests for jotai/utils. Would someone work on it?
I've encountered a few error when I setting up at both LINUX & Windows environment.
The reason of my linux environment is I use npm install
instead yarn install
, while windows couldn't execute the command like mv
, cp
and rm
To avoid other new-to-jotai contributor and users who want to build jotai at their local environment, I suggest we update a setup tutorial at the bottom of README.md, and create a simple script to classify user's environment and assign appropriate query to execute.
I've already create the os classify script and the tutorial at my local environment, and I hope I can send these files as a Pull Request.
git clone https://github.com/pmndrs/jotai.git
cd ./jotai
npm install
Great work thank you!!
I was thinking how could I use atoms in a non singleton way, for example if I'm making a reusable Component Dropdown
& I wanna hold onto some state for each instance of the Component in atoms like for a dropdown it would be options
, how do I do that ?
I wonder if there's gonna be support for atoms family, like Recoil has: https://recoiljs.org/docs/api-reference/utils/atomFamily
Hey let's say I have a Parent
component that fetches data and renders a list of that data. So far so good. Then for each element of the parent data fetched a Child
component is rendered that uses atomFamily
. Now each Child
component initialized the atomFamily
with the same ID, so I thought the caching mechanism should kick in and after it sees that data was fetched once from the atomFamily
, subsequent calls with useAtom
in other children will not make HTTP requests anymore but will read data from cache.
codesandobox: https://codesandbox.io/s/delicate-rain-f89kt You can open the console in the network tab to see that Child
is making as many HTTP requests as list elements in Parent
Allow primitive atoms to define reducers (setters), which will allow to transform the update. I don't think you should need to create a pair of a primitive and a derived atoms (where you don't export the primitive atom at all) just to accomplish this.
It seems it's somewhat supported even now, but it's undocumented and not typed correctly:
type List = { id: string; value: number }[];
const sortedListAtom = atom<List, List>([], (get, set, update) => {
return set(
sortedListAtom,
update.sort((a, b) => a.value - b.value)
);
});
Use case: transform updates so that callers don't have to do it themselves in each call site. In the above example, we want to update the list and ensure it's sorted. The updater may be much more complex and you don't want to force (and ensure) consumers do that themselves.
Suppose i am storing some authentication state of a user in an atom and i want to call an API with that state. So how can i do that outside the functional component?
Hey,
I have an atom like this:
const test = atom({
one: 'foo',
two: 'bar',
cache: {}
});
In my code I want to do the following:
setTest({
cache: {
test: true,
},
});
Is this supported? Currently, you have to spread ...test
inside of the setTest
, however, this does not deep merge all objects inside of cache
Hi everyone, really appreciate the work done on jotai.
I've been spending hours today trying to understand why set
wouldn't accept my atom's Value
type but instead accepts the Update
type. Codesandbox here.
Can anyone enlighten me?
import { atom } from "jotai";
export interface Person {
name: string;
age: number;
}
export interface Action<T> {
type: string;
payload: T;
}
export const personAtom = atom<Person, Action<any>>(
() => ({
name: "Joe",
age: 55
}),
(get, set, update) => {
const newPerson = {
name: "Jon",
age: 65,
[update.type]: update.payload
};
set(personAtom, newPerson);
// ~~~~~~~~~
// TypeScript error here ^
// but why am I setting personAtom with an update value?
// shouldn't it be the real value? i.e. Person?
}
);
hi
I'm curious. Can I use Jotai with React Native?
Thanks
with Jotai, the <FakeCard />
will re-render whenever editId changed, but Recoil.js does not because i can select only setter function. How to achieve the same behavior with Jotai?
import React from 'react';
import { atom, useRecoilState, useSetRecoilState } from 'recoil';
const editIdAtom = atom<number | null>({
key: `editIdAtom`,
default: null,
});
function FakeCard() {
const setEditId = useSetRecoilState(editIdAtom);
const handleClick = React.useCallback(
(e: React.MouseEvent<HTMLAnchorElement>) => {
setEditId(1);
},
[setEditId]
);
return (
<div>
<a onClick={handleClick}>toggle</a>
<FakeModal />
</div>
);
}
function FakeModal() {
const [editId, setEditId] = useRecoilState(editIdAtom);
const handleClose = React.useCallback(() => {
setEditId(null);
}, [setEditId]);
return editId ? <span>editing {editId}</span> : null;
}
import React from 'react';
import { atom, useAtom } from 'jotai';
const editIdAtom = atom<number | null>(null);
function FakeCard() {
const [,setEditId] = useAtom(editIdAtom);
const handleClick = React.useCallback(
(e: React.MouseEvent<HTMLAnchorElement>) => {
setEditId(1);
},
[setEditId]
);
return (
<div>
<a onClick={handleClick}>toggle</a>
<FakeModal />
</div>
);
}
function FakeModal() {
const [editId, setEditId] = useAtom(editIdAtom);
const handleClose = React.useCallback(() => {
setEditId(null);
}, [setEditId]);
return editId ? <span>editing {editId}</span> : null;
}
@dai-shi's example sandbox: https://codesandbox.io/s/nextjs-with-jotai-async-pm0l8?file=/pages/_app.js
Clicking the button rapidly immediately opts-out of the fallback UI and shows a stale success state, then switches to the current state.
Suspense should not opt-out of fallback until the current Promise state has been resolved.
it seem to be an issue with use-context-select
@dai-shi .
Can you please update the README with one line or small paragraph on how jotai know what the dependents are for an atom with them? ie. In the example of const uppercaseAtom = atom(get => get(textAtom).toUpperCase())
how does it know that textAtom
is a dependent of uppercaseAtom
and call the callback function only if textAtom
updates.
I've sadly failed to confirm after throwing a glance at the source, but I'm guessing it determines all dependents on first actual use of the atom? similar to how hooks work.
Lifecycle transparency is very important when building anything slightly more complex then a simple example; in particular with anything async. If you ever build a docs site a small section to it would be appreciated, since when things are actually likely to get garbage collected (if ever) is another very-good-to-know tidbit to avoid shooting oneself in the foot on more complex interactions (eg. extremely large table/dataset visualization).
You should also include a section on non-singletons (since those also can have weird problems with what/when/how) and typescript types (how the typescript types need to look for example), which I assume is something like...
interface LocalState {
emailAtom: Atom<string>
messageAtom: Atom<string>
transportAtom: Atom<{ send: () => Promise<void> }>
// ...any other static state
}
function useInit(): LocalState {
let local = useRef<LocalState>();
if (local.current == undefined) {
let emailAtom = atom("");
let messageAtom = atom("Hello, I can not ");
const transportAtom = atom((get) => {
let email = get(emailAtom);
let message = get(messageAtom);
return {
send: async () => {
console.log("SEND", { email, message });
}
};
});
local.current = {
emailAtom,
messageAtom,
transportAtom
// ...any other static state
}
}
return local.current;
}
function Input({ atom }) {
let [ text, update ] = useAtom(atom);
return <input value={text} onChange={event => update(event.target.value)}/>
}
export function ReusableContactForm() {
let {
emailAtom, messageAtom, transportAtom
} = useInit();
let [ transport ] = useAtom(transportAtom);
return (
<>
<Input atom={emailAtom}/>
<Input atom={messageAtom}/>
<button onClick={() => transport.send()}>Submit</button>
</>
);
}
JS Snippet Version: https://codesandbox.io/s/jotai-demo-forked-qh3qj?file=/src/App.js
The above also illustrates how for example (because atom
internal workings are not very well explained) how you may encounter small gochas with non-obvious explanations (not that its ever obvious when a system gets big enough). In this case why cant you just use a function as a atom value and have to use that transport pattern instead. With out clarifying these use cases its unclear if thats the correct pattern as well; if there was a way to retrieve the value with out going though the entire react dance (and limitations of hooks in conditionals) then send
could simply be a a static function declared in the useInit
function that simply uses the atoms declared earlier in the scope; it's not easy to figure out why that's a bad idea or cant be done or if it's actually possible just not obvious.
I'm under the impression that the website is broken on Safari, at least with iOS 14 and macOS Safari 14.
I am not able to enter text in the input field - in iOS the keyboard pops up, but typing doesn't have any effect.
On Desktop, the field seems selected, but i get no cursor, and typing has no effect.
This seems to be caused by " * {user-select: none;}".
(On a related note, i also found it very irritating to be unable to select input text, especially when trying to paste stuff instead of typing on mobile)
immutableMap is an internal module. It's an abstract module.
I wonder if anyone would be interested in writing tests for it.
Note that we want to test the api, not the implementation (the type ImmutableMap
is representing implementation), because the goal is to come up with better implementation with same functionality.
Core tests are the followings:
tests/async.test.tsx
tests/basic.test.tsx
tests/dependency.test.tsx
tests/error.test.tsx
tests/items.test.tsx
I need someone to help advice if it's ok or if there's room for improvement. I followed the same pattern as in zustand, but I'm not quite sure if testing with findByText
is a good/desired way (instead of using expect
.)
While the atom model fits with bottom-up approach (composing small atoms), we could create a big atom and select pieces out of it.
const bigAtom = atom({
num: 0,
str: 'hi',
bool: true,
obj: { a: 1, b: 2, arr: [3, 4, 5] },
})
const numAtom = atom(
get => get(bigAtom).num,
(get, set, update) => {
const prev = get(bigAtom)
const num = typeof update === 'function' ? update(prev.num) : update
set(bigAtom, { ...prev, num })
}
)
const [num, setNum] = useAtom(numAtom)
We can make this a hook. Let's try for a read-only atom.
const useSelector = (anAtom, selector) => {
const selectedAtom = useMemo(() => atom(get => selector(get(anAtom))), [anAtom, selector])
return useAtom(selectedAtom)[0]
}
The callback of useContextSelector
called in render phase. So I think this line is a side effect in render phase.
Hello,
I encounter an issue with the following snippet:
const value = atom(1)
const double = atom(get => get(value) * 2)
const triple = atom(get => get(double) * 3)
// Dependency chain
// triple --(depends on)--> double --(depends on)--> value
When I update value
, double
is updated as well but triple
is not. triple
is updated only after a second render. The following snippet explain the sequence:
//Initial state
value => 1
double => 2
triple => 6
// value <= 2
value => 2
double => 4
triple => 6
// Another event cause the component to re-render
value => 2
double => 4
type => 12
I would expect the snippet above to work as:
//Initial state
value => 1
double => 2
triple => 6
// value <= 2
value => 2
double => 4
triple => 12
It seems that level > 1 dependency relationships between atoms are not handled well.
Hey @dai-shi, I don't have any clue how to start the development of the project, Could you explain how to start the project as watch mode and ... for development, and how to see the result.
useAtom with primitive atoms is analogous to useState. We can create something similar to useReducer.
const countAtom = atom(0)
const countReducer = (prev, action) => {
if (action.type === 'inc') {
return prev + 1
} else if (action.type === 'dec') {
return prev - 1
} else {
throw new Error('unknown action type')
}
}
const countReducerAtom = atom(
get => get(countAtom),
(get, set, action) => set(countAtom, countReducer(get(countAtom), action)
)
const Component = () => {
const [count, setCount] = useAtom(countAtom) // useState style
const [count2, dispatch] = useAtom(countReducerAtom) // useReducer style
// ...
}
Hi @dai-shi, first of all thank you for this library. It is great to have different options, approaches and improvements around the React state topic. Good job!
I'm playing a bit with it and trying to better understand what's happening under the hood, which use cases can be easily covered with jotai and which doesn't (if any). And several questions arised. I didn't check code yet, sorry, so my questions are based on guessing.
My first assumption when I saw the Provider
wrapping the tree:
<Provider>
<App />
</Provider>
and also based on this quote:
If you need React Context alternatives, Jotai comes with enough features
was to think that:
Also, for learning/debug purposes:
this is a very weird use-case but sometimes I wanna just update an atom without re-render ?
is that possible ? if so how ?
It's not supported by the lib itself, but here's an idea how to persist data.
const strAtom = atom(localStorage.getItem('myKey') ?? 'foo')
const strAtomWithPersistence = atom(
get => get(strAtom),
(get, set, newStr) => {
set(strAtom, newStr)
localStorage.setItem('myKey', newStr)
},
)
or
const strAtom = atom('foo')
const persistStrAtom = atom(
null,
(get, set, action) => {
if (action.type === 'init') {
const str = localStorage.getItem('myKey')
if (str !== null) {
set(strAtom, str)
}
} else if (action.type === 'set') {
set(strAtom, action.str)
localStorage.setItem('myKey', str)
}
},
)
What would be the best way to imperatively refresh an atomFamily
?
Let's say I have following case, where I use get(updateAtomFamily)
in order to track it as dependency in the atomFamily
so that I trigger the fetch imperatively:
export const fetchAtomFamily = atomFamily(
(param) => async (get) => {
get(updateAtomFamily);
const { data } = await getData({
param1: param.param1,
param2: param.param2,
});
},
null,
deepEqual
);
export const updateAtomFamily = atom(0);
Then my idea was to imperatively useUpdateAtom
in my component as a refresh function like this:
const refresh= useUpdateAtom(updateAtomFamily);
and then have the imperative call:
<button onClick={() => refresh(prev => prev + 1)}>refresh</button>
However, when I trigger the refresh function, I get following error:
Uncaught Error: atom state not found. possibly a bug.
In my opinion however, even if this approach worked, it feels sluggish and cumbersome to me. It would be extremely helpful if the API had a native approach to re-fetch async atoms :)
#83 added some drafts. we need to improve them.
There would be several levels of contributions.
Feel free to ask general questions in this issue. Also we have a channel in the pmndrs Discord server.
There’s a index.cjs
file in the distribution, but no utils.cjs
, is there any reason? I’m trying to debug an error with jest so I’m just wondering. Thanks!
Hello,
Interesting library!
Would it be possible for you to provide utilities for using this in conjunction with a optics library? My idea for a pretty nice api would be:
const bigAtom: WritableAtom<{myKey: number[]}> = atom({myKey: [1,2,3]})
const focusedAtom: WritableAtom<number> = bigAtom.focus(optic => optic.prop('myKey').index(0))
Or even:
const bigAtom: WritableAtom<{myKey: 10}> = atom({myKey: 10})
const focusedAtom: WritableAtom<number> = bigAtom.prop('myKey').index(0)
The optics library I like is https://github.com/akheron/optics-ts
The idea is that if any of the focused atoms are "set", then the big atom is updated, leading to a update to the focused atom.
Reported in Discord channel.
Reproduction: https://codesandbox.io/s/competent-rubin-zf1nd?file=/src/App.js
Simply by opening it, it shows an error. 😱
It's a refreshing and beautiful take on state management, fitting perfectly my usecase!
Going through the source code, I don't see any re/hydration mechanism.
@dai-shi : If Jotai is SSR-ready or close to, could you set up an example? Or point out how?
Thanks!
@gsimone suggested useImmerAtom:
const useImmerAtom = (anAtom) => {
const [state, setState] = useAtom(anAtom)
const setStateWithImmer = useCallback((fn) => {
setState(produce((draft) => fn(draft)))
}, [setState])
return [state, setStateWithImmer]
}
Or, we could create a writable atom:
const countAtom = atom(0)
const countWithImmerAtom = atom(
(get) => get(countAtom),
(get, set, fn) => set(countAtom, produce((draft) => fn(draft)))
)
We might be able to create a helper function.
const warpWithImmer = (anAtom) => atom(
(get) => get(anAtom),
(get, set, fn) => set(anAtom, produce(get(anAtom), (draft) => fn(draft)))
)
I'm trying out jotai in one of my projects and it was crashing with code like this
React.useEffect(() => {
setState(s => {
const x = {};
delete x[123][123];
});
});
and I was confused why this error wasn't caught by the react error boundary (of react-error-boundary
fame). Then I realized that my component three was like this JotaiProvider -> ErrorBoundary -> MyComponent
so doing something like ErrorBoundary -> JotaiProvider -> ErrorBoundary -> MyComponent
helped, but it makes me realize interesting consideration that with smaller context providers I can have them closer to my component and thus making errors more local, but errors in jotai are basically globally scoped :)
I don't know if that's an issue per se, but maybe a good point to consider when deciding between custom context vs global context
Unlike zustand, jotai doesn't work across rerenderers.
This is because jotai is React state oriented.
Also partly intentional to distinguish jotai from zustand.
We might be able to provide an escape hatch for this if there's certain demand and use cases.
Refs:
Atoms can hold any primitive values, including objects.
Here's an example to updating a property of an object.
const funkyPigAtom = atom({ funkyPig: { glasses: 'stylish', bling: 'golden' }})
const Component = () => {
const [funkyPig, setFunkyPig] = useAtom(funkyPigAtom)
const updateBling = (bling) => {
setFunkcyPig((prev) => ({ ...prev, funkyPig: { ...prev.funkyPig, bling } }))
}
// ...
}
We could also create an update action atom.
const funkyPigAtom = atom({ funkyPig: { glasses: 'stylish', bling: 'golden' }})
const updateBling = atom(
null,
(get, set, bling) => {
const prev = get(funkyPigAtom)
set(funkyPigAtom, { ...prev, funkyPig: { ...prev.funkyPig, bling } })
}
)
Jotai means "state" in Japanese.
Zustand means "state" in German.
Jotai is close to Recoil.
Zustand is close to Redux.
Jotai state is within React component tree.
Zustand state is in the store outside React.
Jotai state consists of atoms (i.e. bottom-up).
Zustand state is one object (i.e. top-down).
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.