Skip to content
A bike, for no reason
Wednesday, 25 January 2023

At 547 words, this article should take about 2 minutes to read.

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.

Cover image courtesy of Mikkel Bech.


Fin

Comments

In almost all cases, the comments section is a vile cesspool of Reply Guys, racists, and bots.

I don't want to have to deal with that kind of hell so I don't have a comments section.

If you want to continue the conversation, you can always hit me up on Mastodon (which is not a vile cesspool of Reply Guys, racists, and bots).

Thomas Rigby

Thomas Rigby

When I'm not building things for the internet, I take photos of stuff.

Pronouns: he/him/his

Real. Simple. Syndication.

Get my latest content in your favorite RSS reader. I use InoReader but you don't have to.

What even is an RSS feed?!

Subscribe

Last played

Burn the Bitch

Ulver || Svidd Neger

Listen
Loading Invisible Visible RSS Navigation Close Arrow Info Online Online