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:
- Provider wrapping your app
- useSelector to read state
- useDispatch to get dispatch function
- dispatch(action()) to update state
With Zustand:
- 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
import useListingsStore from '@/state/useListingsStore';
function HomePage() {
// Use the store here...
}Step 2: Select State
Use the store hook with a selector function:
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 arrayThink 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:
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:
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!
- Select what you need (
favorites,toggleFavorite) - Use it (
favorites.includes(),toggleFavorite()) - 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
| Task | Code |
|---|---|
| Select state | const items = useListingsStore((state) => state.items) |
| Select action | const setItems = useListingsStore((state) => state.setItems) |
| Call action | setItems(newData) ← No dispatch! |
| Select multiple | Multiple useListingsStore() calls |
| Get state outside component | useListingsStore.getState().items |
| Computed value | const count = useListingsStore((state) => state.items.length) |
What's Next?
Perfect! You know how to connect components to Zustand. In the next lesson:
- Async actions - Fetch data from API
- Loading states - Handle status and error
- Side effects - Where to put async logic
- 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