Experience of using Jotai to extract component state

Original link: https://innei.in/posts/programming/jotai-experience-with-component-state-abstraction

This rendering is generated by marked, and there may be typographical problems. For the best experience, please go to: https://innei.in/posts/programming/jotai-experience-with-component-state-abstraction

Jotai is a very good atomic model based React state management library. It takes a bottom-up approach to building global React state management, building state by composing atoms, and optimizing rendering based on atomic dependencies. This solves the extra re-render of the React context and removes the need for memoization.

Using Jotai can easily split the state (State) of the top-level components into multiple Atoms, separate the state from the UI, and manage the state efficiently. You can also get the state Getter / on-demand inside the component Setter, reducing a lot of re-rendering.

Use useAtom instead of useState

The usage of Jotai atom is very simple. Generally, you only need to write the initialValue of useState into atom() , and then directly replace useState .

 const isShowAtom = atom(true) const Foo = () => { const [isShow, setIsShow] = useState(true) // change to const [isShow, setIsShow] = useAtom(isShowAtom) }

In addition, Jotai also provides useAtomValue and useSetAtomValue , which can be used on demand. In the scenario of not consuming atomValue, you don’t need to use useAtom , but you can use useSetAtomValue directly. The advantage is that when the atomValue changes, the component will not be re-rendered. For example, in the following example, when the parent component clicks the Button, no re-rendering will occur, and only the Bar will be updated:

 const isShowAtom = atom(true) const Foo = () => { const setIsShow = useSetAtom(isShowAtom) return <> <Bar /> <button onClick={() => setIsShow(true)}>Show</button> </> } const Bar = () => { const isShow = useAtomValue(isShowAtom) // ... }

::: info

Off-topic: If I don’t use Jotai, how should I avoid excessive multi-rendering caused by useState?

In the above example, we can use useState + useContext instead.

 const IsShowContext = createContext(false) const SetIsShowContext = createContext<SetStateAction<boolean>>(() => {}) const Foo = () => { const [isShow, setIsShow] = useState(false) return ( <IsShowContext.Provider value={isShow}> <SetIsShowContext.Provider value={setIsShow}> <FooImpl /> </SetIsShowContext.Provider> </IsShowContext.Provider> ) } const FooImpl = () => { const setIsShow = useContext(SetIsShowContext) return ( <> <Bar /> <button onClick={() => setIsShow(true)}>Show</button> </> ) } const Bar = () => { const isShow = useContext(IsShowContext) // ... }

And this method of breaking the Context for performance optimization is very difficult to maintain when there are too many states.

If you don’t want to use a state library similar to Jotai, you can try foxact ‘s useContextState implementation, which is similar to the above.

:::

Use useContext and atom to shrink the global state into the component

After using useAtom to replace useState above, the component state is externalized, so that the component cannot be reused. Once it is reused, its state is shared. We can use useContext and atom to shrink the state of the component to the inside of the component.

Still take the simple example above as an example.

 const FooContext = createContext(null) const Foo = () => { const contextValue = useRef({ isShowAtom: atom(false), }).current return ( <FooContext.Provider value={contextValue}> <FooImpl /> </FooContext.Provider> ) } const FooImpl = () => { const { isShowAtom } = useContext(FooContext) const setIsShow = useSetAtom(isShowAtom) return ( <> <Bar /> <button onClick={() => setIsShow(true)}>Show</button> </> ) } const Bar = () => { const { isShowAtom } = useContext(FooContext) const isShow = useAtomValue(isShowAtom) // ... }

Use a context to subtly enter the state into the component again, pass in a contextValue from the top layer, mount atom inside the context, and consume the atom on demand, and because the Context has no dependencies, you can create any component in the built-in component The use of contextValue does not need to worry about the re-rendering problem caused by the change of the contextValue, where the contextValue will never change, and thanks to Jotai, get the atom from the contextValue anywhere and then use useAtomValue to subscribe to the change of the atom and respond to the component.

On-Demand Subscriptions to Atom – selectAtom

Jotai provides selectAtom function, which can create a new readOnly atom based on the original atom. The main method of use is to implement selector.

If the general atomValue itself is a primitive type, there is no need to use this function. If it is a value of reference type, such as an object. According to the characteristics of Immutable, if a huge object needs to be changed internally, a new object will be created. Any changes inside the object will cause the components subscribed to the entire atomValue to be updated.

 const objectAtom = atom({ type: 'object', value: { foo: 1 } }) const Foo = () => { const setter = useSetAtom(objectAtom) return <> <Bar /> <button onClick={() => setter(prev => ({...prev, value: { foo:1 }}))}>Show</button> </> } const Bar = () => { const { type } = useAtomValue(objectAtom) // ... }

In the above example, although Bar only consumes type , after the button in Foo is clicked, Bar will also change because the value of objectAtom changes.

 const objectAtom = atom({ type: 'object', value: { foo: 1, }, }) const Foo = () => { const setter = useSetAtom(objectAtom) return ( <> <Bar /> <button onClick={() => setter((prev) => ({ ...prev, value: { foo: 1 } }))} > Show </button> </> ) } const Bar = () => { const type = useAtomValue( selectAtom( objectAtom, useCallback((atomValue) => atomValue.type, []), // 注意这里), ) // ... }

However, using selectAtom to extract the internal value will not cause such a problem. Note that you need to use useCallback to wrap the incoming selector, or extract this function to the outside of the component. You need to ensure that the function remains unchanged in the next re-rendering, which will cause re-rendering hell.


The above three points can cover most scenarios. Hurry up and try it.

finish watching? say something

This article is transferred from: https://innei.in/posts/programming/jotai-experience-with-component-state-abstraction
This site is only for collection, and the copyright belongs to the original author.