Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Connecting React Components to the Store with Hooks

Hello! Welcome back to our course on Zustand.

In the last lesson, we defined our first Zustand store, useCounterStore, complete with an initial state and actions to modify it. We now have a self-contained unit of state management, but it's not yet connected to our application's UI.

Today, we'll bridge that gap. We will bring our store to life by learning how to access its state and actions from within our React components. This is where you'll see the simplicity and power of Zustand's hook-based API.

Learning Outcome: By the end of this lesson, you will be able to access state and actions from the store within a React component using the generated hook.

1. The Store is a Hook

The most important concept to grasp is that the useCounterStore we created is a custom React hook. Because it's a hook, you can call it directly inside your functional components to "hook into" the store.

Unlike React's built-in useContext hook, Zustand's hook doesn't require you to wrap your component tree in a Provider. You can simply import your store and use it anywhere you need.

2. Accessing State with Selectors

To read data from the store, you call the hook and pass it a selector function. This function receives the entire state object as its argument and should return the specific piece of state your component needs.

const value = useCounterStore(state => state.value);

This selector mechanism is the key to Zustand's performance. A component that uses a selector will only re-render if the value returned by that selector changes. If other parts of the state are updated, your component remains unaffected.

The following video provides a clear demonstration of this process.

Zustand - Complete Tutorial

This clip from the 'Zustand - Complete Tutorial' by Cosden Solutions shows exactly how to call the generated hook within a component and use a selector function to retrieve a specific state property.

Watch from 04:09 to 05:29. Notice how the useCounterStore hook is called with a function state => state.count to extract just the count value.

3. Accessing Actions

Accessing actions works exactly the same way as accessing state. You call the store hook with a selector function that picks the action you need.

const increment = useCounterStore(state => state.increment);

Since actions are functions and their references don't typically change, components that only select actions will not re-render when the state values themselves change.

Let's see how to get our actions and wire them up to UI elements like buttons.

Zustand - Complete Tutorial

Continuing with the same tutorial, this section demonstrates how to select actions from the store and attach them to onClick handlers in a React component.

Watch from 05:29 to 09:03. Observe how separate constants are created for increment and decrement by calling the hook with different selectors. The video also briefly touches on an important concept we'll explore in the next lesson: updating state based on the previous state.

The official GitHub README provides a very concise text-based example of this pattern, splitting the display and control logic into two separate components.

Zustand GitHub README

The main Zustand README on GitHub offers a clear, minimal example of binding state and actions to components. This is a great reference for the fundamental pattern.

Review the code snippets under the heading 'Then bind your components, and that's it!'. Note how BearCounter selects state, and Controls selects an action.

4. Hands-On: Connecting the counterStore

Now, let's apply what you've learned to the zustand-sandbox project. We'll modify App.tsx to use our useCounterStore.

Replace the entire content of src/App.tsx with the following code. We'll create two simple components inside this file to demonstrate how they can independently connect to the same store.

import { useCounterStore } from './store/counterStore';
import './App.css';

// Component to display the count
function CounterDisplay() {
  // Selects only the 'count' state.
  // This component will re-render ONLY when 'count' changes.
  const count = useCounterStore((state) => state.count);

  return <h1>Count: {count}</h1>;
}

// Component with buttons to modify the count
function CounterControls() {
  // Selects the actions. Since these are stable functions,
  // this component will not re-render when 'count' changes.
  const increment = useCounterStore((state) => state.increment);
  const decrement = useCounterStore((state) => state.decrement);
  const reset = useCounterStore((state) => state.reset);

  return (
    <div>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset to 0</button>
    </div>
  );
}

function App() {
  return (
    <>
      <p>
        These two components are separate, but they share state through the
        Zustand store.
      </p>
      <CounterDisplay />
      <CounterControls />
    </>
  );
}

export default App;

After updating the file, run your development server (npm run dev). You should see the counter working correctly. Notice how CounterDisplay updates in response to clicks in CounterControls, even though they are sibling components with no props passed between them. This is the essence of global state management.

5. A Quick Note on Selector Performance

You might be tempted to select the entire state object at once, like this:

const state = useCounterStore();

While this is possible, it's generally an anti-pattern.

Zustand GitHub README

The Zustand README explains the consequence of fetching the entire state object.

Read the short section 'Fetching everything'.

As the documentation states, this will cause your component to re-render on every state change, defeating the purpose of selective subscriptions. Always select the smallest amount of state your component needs. If you need multiple values, use multiple selectors:

const nuts = useBearStore((state) => state.nuts);
const honey = useBearStore((state) => state.honey);

We will explore more advanced selector patterns for performance optimization in Module 4. For now, the key takeaway is: be specific with your selectors.

Conclusion

In this lesson, you learned the fundamental pattern for connecting Zustand to your React application.

  • The store is a hook: The create function returns a custom hook that you can use in any component.
  • Selectors access data: You call the hook with a selector function (state => state.pieceOfState) to read state or actions.
  • Selectors optimize renders: Components re-render only when the specific data they've selected changes.
  • No providers needed: Zustand's global state is accessible without wrapping your app in a context provider, reducing boilerplate.

You are now able to both create a Zustand store and consume it within your React components.

Next Lesson Preview:
In the next lesson, we will begin Module 2 by taking a closer look at the set function. You will learn the difference between merging state and replacing state, a crucial detail for correctly managing complex state objects.

Can't find a good explanation? Sign up and we'll make it for you

Sign up