Info Zustand state manager is the simplest state manager ever. Installation npm install zustand App Store import create from 'zustand' const useStore = create(set => ({ counter: 0, add: (num) => set((state) => ({ counter: state.counter + (num || 1) })), reset: () => set({ counter: 0 }), isLogged: false, logInOut: () => set((state) => ({ isLogged: !state.isLogged })), deleteStates: () => set({}, true) })) Set set({ counter: 0 }) pushes counter state into the store object, merges by default set({ counter: 0 }, true) replaces whole store with counter state set((state) => ({ counter: state.counter + 1 })) pushes updated counter state into the store object Reactive binding Select your state and the component will re-render on changes. Whole store Get the whole store. const state = useStore(); It will cause the component to update on every store's state change Specific state const counter = useStore(state => state.counter) Custom equality for re-render const counterWithCustomEqualityFn = useStore( state => state.counter, (prevState, newState) => { if (newState > 5) return false // render if (newState <= 5) return true // no render } ) Async actions const useStore = create(set => ({ fishes: {}, fetch: async pond => { const response = await fetch(pond) set({ fishes: await response.json() }) } })) Read from state in actions const useStore = create((set, get) => ({ sound: "grunt", action: () => { const sound = get().sound // ... } }) Non-reactive reading const useStore = create(() => ({ paw: true, snout: true, fur: true })) // Getting non-reactive fresh state const paw = useStore.getState().paw Set state outside store useStore.setState({ paw: false }) Subscribe for all changes // Listening to all changes, fires synchronously on every change const unsubscribe = useStore.subscribe(console.log) // Unsubscribe listeners unsubscribe() // Destroying the store (removing all listeners) useStore.destroy() Subscribe for state changes Need to add middleware import create from 'zustand' import { subscribeWithSelector } from 'zustand/middleware' const useStore = create(subscribeWithSelector((set, get) => ({ isLogged: false, logInOut: () => set((state) => ({ isLogged: !state.isLogged })), }))) const unsubscribe = useStore.subscribe(state => state.isLogged, (prev, now) => { console.log('triggered log in/out', 'prev state', prev, 'state now', now)}) function Component() { const isLogged = useStore(state => state.isLogged) const logInOut = useStore(state => state.logInOut) return ( <div style={style}> <div>isLogged: <strong>{isLogged?.toString()}</strong></div> <button onClick={() => logInOut()}>Log in/out</button> </div> ) } Immer import create from 'zustand' import produce from 'immer' import { immer } from 'zustand/middleware/immer' const useStore = create(immer((set, get) => ({ counter: 0, add: (num) => set((state) => ({ counter: state.counter + (num || 1) })), addWithImmer: (num) => set((state) => produce(state, (state) => { state.counter = state.counter + (num || 1) })), addWithImmerCurried: (num) => set(produce((state) => { state.counter = state.counter + (num || 1) })), addWithImmerMW: (num) => set((state) => { state.counter = state.counter + (num || 1) }), }))) Devtools Add redux devtools as a middleware import { subscribeWithSelector, devtools} from 'zustand/middleware' const useStore = create(immer(subscribeWithSelector(devtools((set, get) => ({ counter: 0, add: (num) => set((state) => ({ counter: state.counter + (num || 1) }), false, 'ACTION NAME'), // false means 'do not overwrite' the state, but merge it }))))) Devtools can not dispatch an action, but can register it if we provide actions name as 3rd argument in set function. Whole code import create from 'zustand' import { subscribeWithSelector, devtools } from 'zustand/middleware' import { immer } from 'zustand/middleware/immer' import axios from 'axios' import sleeper from '/functions/sleeper' import produce from 'immer' // #region STORE const useStore = create(immer(subscribeWithSelector(devtools((set, get) => ({ counter: 0, add: (num) => set((state) => ({ counter: state.counter + (num || 1) })), addWithImmer: (num) => set((state) => produce(state, (state) => { state.counter = state.counter + (num || 1) })), addWithImmerCurried: (num) => set(produce((state) => { state.counter = state.counter + (num || 1) })), addWithImmerMW: (num) => set((state) => { state.counter = state.counter + (num || 1) }), reset: () => set({ counter: 0 }), isLogged: false, logInOut: () => set((state) => ({ isLogged: !state.isLogged }), false, 'log in or out'), deleteStates: () => set({}, true), users: [], usersLoading: false, fetchUsers: async () => { set({ usersLoading: true }) const res = await axios.get('https://jsonplaceholder.typicode.com/users') await sleeper(3000)() const users = res.data set({ users, usersLoading: false }) }, alertCounter: () => alert(get().counter.toString()) }), { name: 'Zustand store' })))) function App() { const fishes = useStore((state) => state.fishes); const eatFish = useStore((state) => state.eatFish); return ( <div className="App"> <p>Fishes : {fishes}</p> <p> <button onClick={eatFish}>Eat</button> </p> </div> ) } const unsubscribe = useStore.subscribe(state => state.isLogged, (prev, now) => { console.log('triggered log in/out', 'prev state', prev, 'state now', now) }) // #endregion // #region Component function Component() { const counter = useStore(state => state.counter) const counterWithCustomEqualityFn = useStore( state => state.counter, (prevState, newState) => { if (newState > 5) return false // render if (newState <= 5) return true // no render } ) const add = useStore(state => state.add) const addWithImmer = useStore(state => state.addWithImmer) const addWithImmerCurried = useStore(state => state.addWithImmerCurried) const addWithImmerMW = useStore(state => state.addWithImmerMW) const reset = useStore(state => state.reset) const isLogged = useStore(state => state.isLogged) const logInOut = useStore(state => state.logInOut) const deleteStates = useStore(state => state.deleteStates) const users = useStore(state => state.users) const usersLoading = useStore(state => state.usersLoading) const fetchUsers = useStore(state => state.fetchUsers) const alertCounter = useStore(state => state.alertCounter) const style = { border: '2px solid grey', padding: '10px', margin: '10px', maxWidth: '500px' } return ( <div style={style}> <div>Counter: <strong>{counter}</strong></div> <div>Counter with custom equality func: <strong>{counterWithCustomEqualityFn}</strong></div> <button onClick={() => add()}>Increment +1</button> <button onClick={() => addWithImmer()}>Increment with Immer +1</button> <button onClick={() => addWithImmerCurried()}>Increment with Immer Curried +1</button> <button onClick={() => addWithImmerMW()}>Increment with Immer Middleware +1</button> <button onClick={() => add(3)}>Increment +3</button> <button onClick={() => add(-5)}>Decrement -5</button> <button onClick={() => { useStore.setState({ counter: 666 }) }}>Set counter state outside component</button> <button onClick={() => alertCounter()}>Alert counter</button><br /> <button onClick={() => reset()}>Reset</button><br /> <div>isLogged: <strong>{isLogged?.toString()}</strong></div> <button onClick={() => logInOut()}>Log in/out</button><br /> <div>Fetch users</div> <button onClick={() => fetchUsers()}>Fetch users</button><br /> <div> {usersLoading && 'Loading...'} {!usersLoading && !!users?.length && users.map(user => <div key={user.id}>{user.name}</div>)} </div> <div>Replace the store's object content</div> <button onClick={() => deleteStates()}>Delete states</button><br /> </div> ) } // #endregion Split store into slices // posts/zustandStore/counterSlice.js export const counterSlice = (set, get) => ({ counter: 0, add: (num) => set( (state) => ({ counter: state.counter + (num || 1) }), false, 'add' ) }) // posts/zustandStore/loginOutSlice.js export const loginOutSlice = (set, get) => ({ isLogged: false, logInOut: () => set( (state) => ({ isLogged: !state.isLogged }), false, 'log in / out' ) }) // posts/zustandStore/zustandStore.js import create from 'zustand' import { devtools } from 'zustand/middleware' import { counterSlice } from './counterSlice' import { loginOutSlice } from './loginOutSlice' export const zustandStore = create(devtools((set, get) => ({ ...counterSlice(set, get), ...loginOutSlice(set, get) }), { name: 'Store with slices' })) import { zustandStore } from './zustandStore/zustandStore' function ComponentWithStoreFromSlices() { const counter = zustandStore(state => state.counter) const add = zustandStore(state => state.add) const isLogged = zustandStore(state => state.isLogged) const logInOut = zustandStore(state => state.logInOut) const style = { border: '2px solid grey', padding: '10px', margin: '10px', maxWidth: '500px' } return ( <div style={style}> <div>Counter: <strong>{counter}</strong></div> <button onClick={() => add()}>Increment +1</button> <div>isLogged: <strong>{isLogged?.toString()}</strong></div> <button onClick={() => logInOut()}>Log in/out</button><br /> </div> ) }