Code To Learn logo

Code To Learn

M6: State ManagementZustand Path

L3: Connect Zustand to Components

Learn how to use the store in React components with hooks

Let's connect our store to components! 🔌

The Beauty of Zustand

With Redux, you need:

  1. Provider wrapping your app
  2. useSelector to read state
  3. useDispatch to get dispatch function
  4. dispatch(action()) to update state

With Zustand:

  1. Just use the hook! That's it. ✨

Basic Usage

Our store IS a React hook:

// This is the store
const useListingsStore = create((set) => ({...}));

// This is also a hook you can use directly!
const items = useListingsStore((state) => state.items);

No Provider needed! 🎉

Step 1: Import the Store

src/pages/HomePage.jsx
import useListingsStore from '@/state/useListingsStore';

function HomePage() {
  // Use the store here...
}

Step 2: Select State

Use the store hook with a selector function:

src/pages/HomePage.jsx
import useListingsStore from '@/state/useListingsStore';

function HomePage() {
  // Select what you need
  const items = useListingsStore((state) => state.items);
  const status = useListingsStore((state) => state.status);
  const error = useListingsStore((state) => state.error);
  
  return (
    <div>
      <h1>Listings</h1>
      {status === 'loading' && <p>Loading...</p>}
      {status === 'failed' && <p>Error: {error}</p>}
      {status === 'succeeded' && <ul>{items.map(...)}</ul>}
    </div>
  );
}

Understanding Selectors

Selector: A function that picks data from the store

// This is a selector function
(state) => state.items

// Full store state:
{
  items: [...],
  favorites: [...],
  status: 'succeeded',
  error: null
}

// Selector returns just:
[...] // items array

Think of it as a filter that extracts exactly what you need.

Re-render optimization!

// ❌ Bad: Component re-renders on ANY state change
const state = useListingsStore();
const items = state.items;

// Someone toggles favorite
toggleFavorite(5)
// favorites array changed → Component re-renders ❌
// But we don't use favorites! Wasted render!

// ✅ Good: Component only re-renders when items changes
const items = useListingsStore((state) => state.items);

// Someone toggles favorite
toggleFavorite(5)
// favorites changed, but items didn't
// Component doesn't re-render! ✅

Selectors = Performance optimization! 🚀

Pattern 1: Select single field

const items = useListingsStore((state) => state.items);

Pattern 2: Select multiple fields

// Option A: Multiple calls (preferred for optimization)
const items = useListingsStore((state) => state.items);
const status = useListingsStore((state) => state.status);

// Option B: Select object (re-renders when ANY field changes)
const { items, status } = useListingsStore((state) => ({
  items: state.items,
  status: state.status
}));

Pattern 3: Select action

const toggleFavorite = useListingsStore((state) => state.toggleFavorite);

Pattern 4: Derived data

// Get only favorited items
const favoritedItems = useListingsStore((state) =>
  state.items.filter(item => state.favorites.includes(item.id))
);

Step 3: Select Actions

Actions work the same way:

src/pages/HomePage.jsx
function HomePage() {
  // Select state
  const items = useListingsStore((state) => state.items);
  const status = useListingsStore((state) => state.status);
  
  // Select actions
  const setItems = useListingsStore((state) => state.setItems);
  const setStatus = useListingsStore((state) => state.setStatus);
  const setError = useListingsStore((state) => state.setError);
  
  // Use actions directly (no dispatch!)
  const handleFetch = async () => {
    setStatus('loading');
    try {
      const response = await fetch('/api/listings');
      const data = await response.json();
      setItems(data);
      setStatus('succeeded');
    } catch (err) {
      setError(err.message);
      setStatus('failed');
    }
  };
  
  return <div>...</div>;
}

Comparison with Redux

Simple and direct:

import useListingsStore from '@/state/useListingsStore';

function HomePage() {
  // Select state
  const items = useListingsStore((state) => state.items);
  
  // Select action
  const setItems = useListingsStore((state) => state.setItems);
  
  // Call action directly
  setItems(newData);  // ✅ Just call it!
  
  return <div>{items.map(...)}</div>;
}

No Provider, no dispatch, no imports! 🎉

More setup required:

import { useSelector, useDispatch } from 'react-redux';
import { setItems } from '@/state/slices/listingsSlice';

function HomePage() {
  // Select state
  const items = useSelector((state) => state.listings.items);
  
  // Get dispatch function
  const dispatch = useDispatch();
  
  // Dispatch action
  dispatch(setItems(newData));  // Need dispatch wrapper
  
  return <div>{items.map(...)}</div>;
}

Plus need Provider in App.jsx:

import { Provider } from 'react-redux';
import { store } from './state/store';

<Provider store={store}>
  <App />
</Provider>

Real Example: PropertyCard

Let's use the store in PropertyCard for favorites:

src/components/PropertyCard.jsx
import useListingsStore from '@/state/useListingsStore';

function PropertyCard({ listing }) {
  // Select favorites array
  const favorites = useListingsStore((state) => state.favorites);
  
  // Select toggleFavorite action
  const toggleFavorite = useListingsStore((state) => state.toggleFavorite);
  
  // Check if this listing is favorited
  const isFavorited = favorites.includes(listing.id);
  
  return (
    <div className="listing-card">
      <img src={listing.images[0]} alt={listing.title} />
      <h3>{listing.title}</h3>
      <p>${listing.price}/night</p>
      
      <button 
        onClick={() => toggleFavorite(listing.id)}
        className={isFavorited ? 'favorited' : ''}
      >
        {isFavorited ? '❤️ Favorited' : '🤍 Favorite'}
      </button>
    </div>
  );
}

export default PropertyCard;

Look how simple!

  1. Select what you need (favorites, toggleFavorite)
  2. Use it (favorites.includes(), toggleFavorite())
  3. Done! ✅

Understanding Re-renders

Common Patterns

Pattern: Select only what you need

function ListingList() {
  // ✅ Only items - re-renders when items change
  const items = useListingsStore((state) => state.items);
  
  return (
    <div>
      {items.map(item => (
        <PropertyCard key={item.id} listing={item} />
      ))}
    </div>
  );
}

Best for: Components that use a single piece of state

Pattern: Select state and actions together

function FavoriteButton({ listingId }) {
  // Select state
  const favorites = useListingsStore((state) => state.favorites);
  
  // Select action
  const toggleFavorite = useListingsStore((state) => state.toggleFavorite);
  
  const isFavorited = favorites.includes(listingId);
  
  return (
    <button onClick={() => toggleFavorite(listingId)}>
      {isFavorited ? '❤️' : '🤍'}
    </button>
  );
}

Best for: Interactive components that read and update state

Pattern: Derived/computed data

function FavoritesCount() {
  // Compute count from favorites array
  const count = useListingsStore((state) => state.favorites.length);
  
  return <span>Favorites: {count}</span>;
}

Best for: Displaying computed values

Pattern: Complex selector

function FavoritesList() {
  // Get all favorited listings in one selector
  const favoritedListings = useListingsStore((state) =>
    state.items.filter(item => state.favorites.includes(item.id))
  );
  
  return (
    <div>
      {favoritedListings.map(listing => (
        <PropertyCard key={listing.id} listing={listing} />
      ))}
    </div>
  );
}

Best for: Complex data transformations

Warning: This re-renders when items OR favorites changes!

Store Anywhere Pattern

One of Zustand's best features: use the store anywhere!

// ✅ In components
function MyComponent() {
  const items = useListingsStore((state) => state.items);
}

// ✅ In custom hooks
function useListingsData() {
  const items = useListingsStore((state) => state.items);
  const status = useListingsStore((state) => state.status);
  return { items, status };
}

// ✅ In event handlers
const handleClick = () => {
  const currentItems = useListingsStore.getState().items;
  console.log(currentItems);
};

// ✅ Outside components (getState)
import useListingsStore from '@/state/useListingsStore';

export function saveToLocalStorage() {
  const favorites = useListingsStore.getState().favorites;
  localStorage.setItem('favorites', JSON.stringify(favorites));
}

Redux requires: Store must be passed through Provider → Can only access via hooks inside components

Zustand allows: Access the store from anywhere! 🚀

The getState() Method

Quick Reference

TaskCode
Select stateconst items = useListingsStore((state) => state.items)
Select actionconst setItems = useListingsStore((state) => state.setItems)
Call actionsetItems(newData) ← No dispatch!
Select multipleMultiple useListingsStore() calls
Get state outside componentuseListingsStore.getState().items
Computed valueconst count = useListingsStore((state) => state.items.length)

What's Next?

Perfect! You know how to connect components to Zustand. In the next lesson:

  1. Async actions - Fetch data from API
  2. Loading states - Handle status and error
  3. Side effects - Where to put async logic
  4. Compare with Redux thunks

✅ Lesson Complete! You can now use Zustand in components!

Key Takeaways

  • Store is a hook - Use directly in components
  • No Provider needed - Just import and use
  • Selectors optimize - Component only re-renders when selected data changes
  • Actions are just functions - Call directly, no dispatch
  • Use anywhere - Components, hooks, utilities, anywhere!
  • getState() - Access store outside components
  • Super simple - Redux-like power with 90% less code