11 min read
ā¢Question 47 of 47hardState Management Patterns in Next.js
Managing state with Server and Client Components.
State Management
URL State
code.txtTSX
// Use URL for shareable state
'use client';
import { useSearchParams, useRouter } from 'next/navigation';
export function Filters() {
const searchParams = useSearchParams();
const router = useRouter();
const updateFilter = (key: string, value: string) => {
const params = new URLSearchParams(searchParams);
params.set(key, value);
router.push(`?${params.toString()}`);
};
return (
<select onChange={(e) => updateFilter('sort', e.target.value)}>
<option value="newest">Newest</option>
<option value="popular">Popular</option>
</select>
);
}Context for Client State
code.txtTSX
// providers/cart-provider.tsx
'use client';
const CartContext = createContext<CartContextType | null>(null);
export function CartProvider({ children }: { children: React.ReactNode }) {
const [items, setItems] = useState<CartItem[]>([]);
const addItem = (item: CartItem) => {
setItems((prev) => [...prev, item]);
};
return (
<CartContext.Provider value={{ items, addItem }}>
{children}
</CartContext.Provider>
);
}
// In root layout
export default function RootLayout({ children }) {
return (
<html>
<body>
<CartProvider>{children}</CartProvider>
</body>
</html>
);
}Zustand with Next.js
code.txtTSX
// stores/cart-store.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
}
export const useCartStore = create<CartStore>()(
persist(
(set) => ({
items: [],
addItem: (item) => set((state) => ({
items: [...state.items, item]
})),
removeItem: (id) => set((state) => ({
items: state.items.filter((i) => i.id !== id),
})),
}),
{ name: 'cart-storage' }
)
);
// Hydration wrapper for SSR
'use client';
export function CartHydration() {
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
setHydrated(true);
}, []);
if (!hydrated) return null;
return <Cart />;
}