Overview
The useUIStore is a Zustand store that manages global UI state across the application. It handles dark mode toggling, cart drawer visibility, and toast notifications. The store uses Zustand’s persist middleware with partial persistence - only dark mode preference is saved to local storage.
Store Location: src/store/uiStore.js
State Shape
Current theme mode. true for dark mode, false for light mode. This field is persisted to local storage.
Whether the cart drawer is currently open. This field is NOT persisted - resets on page load.
toast
string | null
default: "null"
Current toast message to display. null when no toast is shown. This field is NOT persisted - resets on page load.
Internal counter used to force re-render toast animations. Increments each time a new toast is shown. This field is NOT persisted.
Methods
toggleDarkMode
Toggles between light and dark mode.
const toggleDarkMode = useUIStore (( state ) => state . toggleDarkMode );
toggleDarkMode ();
openCart
Opens the cart drawer/sidebar.
const openCart = useUIStore (( state ) => state . openCart );
openCart ();
closeCart
Closes the cart drawer/sidebar.
const closeCart = useUIStore (( state ) => state . closeCart );
closeCart ();
showToast
Displays a toast notification with the given message. Increments toastKey to trigger animations even for duplicate messages.
The message to display in the toast notification
const showToast = useUIStore (( state ) => state . showToast );
showToast ( "Product added to cart!" );
showToast ( "Order placed successfully" );
hideToast
Hides the currently displayed toast notification.
const hideToast = useUIStore (( state ) => state . hideToast );
hideToast ();
Usage Examples
Dark Mode Toggle
import { useUIStore } from "../../store/uiStore" ;
import { Sun , Moon } from "lucide-react" ;
const Navbar = () => {
const { darkMode , toggleDarkMode } = useUIStore ();
return (
< nav >
< button
onClick = { toggleDarkMode }
aria-label = { darkMode ? "Switch to light mode" : "Switch to dark mode" }
>
{ darkMode ? < Sun size = { 18 } /> : < Moon size = { 18 } /> }
</ button >
</ nav >
);
};
Cart Drawer
import { useUIStore } from "../../store/uiStore" ;
import { useCartStore } from "../../store/useCartStore" ;
import { X } from "lucide-react" ;
export const CartDrawer = () => {
const { isCartOpen , closeCart } = useUIStore ();
const { cart , increaseQty , decreaseQty , removeFromCart } = useCartStore ();
if ( ! isCartOpen ) return null ;
return (
<>
< div className = "drawer-overlay" onClick = { closeCart } />
< div className = "drawer" >
< div className = "drawer-header" >
< h2 > Shopping Cart </ h2 >
< button onClick = { closeCart } >
< X size = { 24 } />
</ button >
</ div >
< div className = "drawer-content" >
{ cart . map (( item ) => (
< div key = { item . id } >
< h3 > { item . title } </ h3 >
< p > $ { item . price } </ p >
< button onClick = { () => decreaseQty ( item . id ) } > - </ button >
< span > { item . qty } </ span >
< button onClick = { () => increaseQty ( item . id ) } > + </ button >
< button onClick = { () => removeFromCart ( item . id ) } > Remove </ button >
</ div >
)) }
</ div >
</ div >
</>
);
};
Toast Notifications
Toast.jsx
ProductCard.jsx
ProductDetail.jsx
import { useUIStore } from "../../store/uiStore" ;
import { useEffect } from "react" ;
export const Toast = () => {
const toast = useUIStore (( state ) => state . toast );
const hideToast = useUIStore (( state ) => state . hideToast );
const toastKey = useUIStore (( state ) => state . toastKey );
useEffect (() => {
if ( toast ) {
const timer = setTimeout (() => {
hideToast ();
}, 3000 );
return () => clearTimeout ( timer );
}
}, [ toast , toastKey , hideToast ]);
if ( ! toast ) return null ;
return (
< div className = "toast" key = { toastKey } >
{ toast }
</ div >
);
};
Local Storage
The UI store uses partial persistence - only the darkMode setting is saved to local storage with the key ui-storage . Cart drawer state and toast messages are intentionally not persisted.
Storage Structure
{
"state" : {
"darkMode" : true
},
"version" : 0
}
Partialize Configuration
The store uses Zustand’s partialize option to control what gets persisted:
{
name : "ui-storage" ,
partialize : ( state ) => ({
darkMode: state . darkMode ,
}),
}
This ensures that:
✅ darkMode preference persists across sessions
❌ isCartOpen resets to false on page load
❌ toast and toastKey reset on page load
Toast Key Pattern
The toastKey field is used to ensure toast animations trigger even when showing the same message twice:
showToast : ( message ) => {
set (( state ) => ({
toast: message ,
toastKey: state . toastKey + 1 , // Forces re-render
}));
}
This is useful in React components that use the key prop:
< div className = "toast" key = { toastKey } >
{ toast }
</ div >
Each time toastKey changes, React will unmount and remount the toast component, retriggering animations.
Best Practices
Selector Optimization : Use specific selectors to avoid unnecessary re-renders
// Good - only re-renders when darkMode changes
const darkMode = useUIStore (( state ) => state . darkMode );
// Good - only re-renders when toast changes
const toast = useUIStore (( state ) => state . toast );
// Avoid - re-renders on any UI state change
const uiState = useUIStore ();
Auto-hide Toasts : Always set a timeout to automatically hide toasts
useEffect (() => {
if ( toast ) {
const timer = setTimeout (() => {
hideToast ();
}, 3000 );
return () => clearTimeout ( timer );
}
}, [ toast , toastKey ]);
Close Drawer on Navigation : Close the cart drawer when navigating to cart page
const navigate = useNavigate ();
const closeCart = useUIStore (( state ) => state . closeCart );
const goToCart = () => {
closeCart ();
navigate ( "/cart" );
};
Dark Mode CSS Variables : Use CSS variables for theming
:root {
--bg-color : #ffffff ;
--text-color : #000000 ;
}
.dark-theme {
--bg-color : #1a1a1a ;
--text-color : #ffffff ;
}
Accessibility : Ensure dark mode toggle has proper ARIA labels
< button
onClick = { toggleDarkMode }
aria-label = { darkMode ? "Switch to light mode" : "Switch to dark mode" }
>
{ darkMode ? < Sun /> : < Moon /> }
</ button >
Common Patterns
Combine Multiple UI States
const Navbar = () => {
const { darkMode , toggleDarkMode , openCart } = useUIStore ();
const cart = useCartStore (( state ) => state . cart );
return (
< nav >
< button onClick = { toggleDarkMode } >
{ darkMode ? "☀️" : "🌙" }
</ button >
< button onClick = { openCart } >
Cart ( { cart . length } )
</ button >
</ nav >
);
};
Toast with Actions
const showSuccessToast = () => {
const showToast = useUIStore . getState (). showToast ;
showToast ( "Product added to cart!" );
};
// Call outside of React component
showSuccessToast ();
Drawer with Backdrop Click
export const CartDrawer = () => {
const { isCartOpen , closeCart } = useUIStore ();
return (
<>
{ isCartOpen && (
<>
< div
className = "overlay"
onClick = { closeCart }
aria-hidden = "true"
/>
< div className = "drawer" >
{ /* Drawer content */ }
</ div >
</>
) }
</>
);
};