One of the downsides to Controller/Reducer-based state management is that a single piece of state needs to be passed from Component to Component up and down the cascade - regardless of whether that component needs the state or not.
This can cause an application to run slower than is desirable.
Zustand promises to alleviate this issue. And, to cut a long story short, it does.
Observe the Hideous Spaceship of StateControllers!
ReactDOM.render(
<React.StrictMode>
<AppState>
<UserState>
<DownloadState>
<DocumentsState>
<ShareState>
<ModalState>
<ViewerState>
<InteractionsState>
<NotificationsState>
<App />
</NotificationsState>
</InteractionsState>
</ViewerState>
</ModalState>
</ShareState>
</DocumentsState>
</DownloadState>
</UserState>
</AppState>
</React.StrictMode>,
document.getElementById("root")
);
This means that a piece of information from say ModalState has to be passed to ViewerState to InteractionsState to NotificationsState before the final destination of <App/> . This causes several unnecessary re-renders as well as being slow, costly, and frankly irritating.
Zustand uses a “Hook-based” approach that doesn’t pass state around components that do not directly need it.
This approach greatly reduces the “spaceship” and looks a little like this;
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<AppRoutes />
</BrowserRouter>
</React.StrictMode>
)
Creating a Store
// Store/app.jsx
import create from 'zustand'
const useAppStore = create((set, get) => ({
openSidebar: () => set(state => ({ sidebarIsOpen: true })),
closeSidebar: () => set(state => ({ sidebarIsOpen: false })),
sidebarIsOpen: false,
});
export default useAppStore;
Now we have a store we can import wherever we need to.
// Components/Nav.jsx
import useAppStore from '../../Store/app'
const Nav = (
) => {
const openSidebar = useAppStore(state => state.openSidebar)
return (
<nav className="primary-nav">
<button onClick={openSidebar}>🍔</button>
<ul className="nav-list">...</ul>
</nav>
)
}
export default Nav
// Components/Sidebar.jsx
import useAppStore from '../../Store/app'
const Sidebar = ({ children }) => {
const isSidebarOpen = useAppStore(state => state.isSidebarOpen)
const closeSidebar = useAppStore(state => state.closeSidebar)
return (
<aside open={isSidebarOpen} className="sidebar">
<button onClick={closeSidebar}>❎</button>
{children}
</aside>
)
}
export default Sidebar
This way, the only Components that are affected by the change in isSidebarOpen are the Nav and the Sidebar. The main body of the app, for example, doesn’t have any knowledge of the state of the sidebar (because it doesn’t need to know).
// Components/Main.jsx
const Main = ({ children }) => <main>{children}</main>
export default Main
However, if we need to, we can make the Main aware of the state change as easily as importing the Store…
// Components/Main.jsx
import useAppStore from '../../Store/app'
const Main = ({ children }) => {
const sidebarIsOpen = useAppStore(state => state.sidebarIsOpen)
return <main className={sidebarIsOpen && 'blur'}>{children}</main>
}
export default Main
Amending state
Imagine, a while down the line, we get the request to track “sidebar opens” (for whatever mad reason - clients, eh?!). The only file we need to change is the Store.
// Store/app.jsx
import create from 'zustand'
import Analytics from 'analytics'
const useAppStore = create((set, get) => ({
openSidebar: () => {
Analytics.log({ event: 'sidebarOpen', timestamp: new Date() })
set(state => ({ sidebarIsOpen: true }))
},
closeSidebar: () => set(state => ({ sidebarIsOpen: false })),
sidebarIsOpen: false,
});
export default useAppStore;
Conclusion
The learning curve is shallow enough - even for someone like me who isn’t the most React-savvy! It makes the state much more readable and replaceable too.

Top comments (0)