Staatsbeheer vormt de kern van elke niet-triviale React-toepassing. Als je het goed doet, wordt je codebase gracieus geschaald; als je het verkeerd doet, krijg je nachtmerries over het boren van props, een verouderde gebruikersinterface en componenten die zichzelf opnieuw in de vergetelheid raken. Het goede nieuws is dat het React-ecosysteem dramatisch volwassen is geworden. Je hebt nu een rijk menu met speciaal gebouwde tools – Redux Toolkit, Zustand, Jotai, TanStack Query en de ingebouwde Context API – elk geoptimaliseerd voor een specifieke probleemklasse.
In dit artikel wordt elke oplossing uitgebreid besproken, worden de afwegingen uitgelegd aan de hand van concrete voorbeelden en krijgt u een beslissingskader dat u onmiddellijk op uw eigen projecten kunt toepassen.
De verschillende soorten staten begrijpen
Voordat u naar een bibliotheek gaat, is het de moeite waard om precies te zijn over wat voor soort staat u feitelijk beheert. Het samenvoegen van verschillende statuscategorieën is de hoofdoorzaak van de meeste overontwikkelde React-applicaties.
- Lokale UI-status — of er een vervolgkeuzelijst geopend is, welk tabblad actief is, de huidige waarde van een gecontroleerde invoer. Deze status is eigendom van een enkele component of een kleine subboom.
- Gedeelde klantstatus – gegevens die meerdere, mogelijk op afstand gelegen componenten moeten lezen of schrijven: de momenteel geauthenticeerde gebruiker, themavoorkeuren, een winkelwagentje.
- Serverstatus — gegevens die afkomstig zijn van een server, asynchroon van aard zijn en cache-invalidatie, opnieuw ophalen op de achtergrond en beheer van de laad-/foutlevenscyclus nodig hebben.
- URL-status - filters, paginering en zoektermen die een paginavernieuwing moeten overleven en via een link kunnen worden gedeeld.
- Vorm staat — gecontroleerde invoer, validatiefouten en indieningsstatus, vaak het beste afgehandeld door een speciale bibliotheek zoals React Hook Form.
Het meest impactvolle wat je kunt doen is weerstand bieden aan de drang om elke staatscategorie in één mondiale winkel te stoppen. Elke categorie heeft verschillende consistentievereisten, verschillende levensduren en verschillende updatefrequenties.
React-context: de ingebouwde optie
De Context API wordt geleverd met React en vereist geen extra afhankelijkheden. Het werkt goed voor een staat die niet vaak verandert en door veel componenten wordt gebruikt. Klassieke voorbeelden zijn thema, landinstelling en het geverifieerde gebruikersobject.
// AuthContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
interface AuthState {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthState | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = async (credentials: Credentials) => {
const user = await authService.login(credentials);
setUser(user);
};
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used inside AuthProvider');
return ctx;
}
De contextprestatieval
Context heeft een bekend prestatiekenmerk waar veel ontwikkelaars last van hebben: elke consument rendert opnieuw wanneer de contextwaardereferentie verandert. Als u een groot, vaak gemuteerd object in één context opslaat, activeert u onnodige herweergave in uw hele boomstructuur.
De mitigatiestrategieën zijn:
- Contexten splitsen op updatefrequentie. Bewaar het gebruikersobject in de ene context en de UI-voorkeuren in een andere.
- Onthoud het waardeobject met
useMemode referentie verandert dus alleen als de gegevens daadwerkelijk veranderen. - Gebruik
React.memoop consumentencomponenten om re-renders over te slaan als de rekwisieten waar ze om geven niet zijn veranderd.
De eerlijke conclusie is dat Context uitstekend geschikt is voor laagfrequente mondiale toestanden, maar dat het geen algemene oplossing voor staatsbeheer is. Zodra je merkt dat je uitgebreide memo's schrijft om het re-rendergedrag van Context te omzeilen, is het tijd om naar een speciale bibliotheek te grijpen.
Redux Toolkit: de keuze voor volwassen ondernemingen
Redux heeft een reputatie op het gebied van boilerplate – een reputatie die het verdiende in het pre-Toolkit-tijdperk. Redux Toolkit (RTK) elimineert de ceremonie en behoudt wat Redux krachtig maakt: een enkele, voorspelbare, inspecteerbare statusboom met strikte unidirectionele gegevensstroom.
RTK-schepen createSlice, dat actiemakers en reducers genereert vanuit één enkele definitie, en Immer onder de motorkap gebruikt, zodat je mutaties kunt schrijven die er noodzakelijk uitzien, maar in werkelijkheid onveranderlijk worden toegepast.
// features/cart/cartSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface CartItem {
id: string;
name: string;
quantity: number;
price: number;
}
interface CartState {
items: CartItem[];
coupon: string | null;
}
const initialState: CartState = { items: [], coupon: null };
export const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addItem(state, action: PayloadAction<CartItem>) {
const existing = state.items.find(i => i.id === action.payload.id);
if (existing) {
existing.quantity += action.payload.quantity;
} else {
state.items.push(action.payload);
}
},
removeItem(state, action: PayloadAction<string>) {
state.items = state.items.filter(i => i.id !== action.payload);
},
applyCoupon(state, action: PayloadAction<string>) {
state.coupon = action.payload;
},
},
});
export const { addItem, removeItem, applyCoupon } = cartSlice.actions;
export default cartSlice.reducer;
RTK-query: serverstatus binnen Redux
RTK levert een metgezel genaamd RTK Query die de serverstatus rechtstreeks binnen de Redux-winkel afhandelt. Het genereert automatisch React-hooks op basis van eindpuntdefinities en beheert caching, invalidatie en optimistische updates.
// services/productsApi.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const productsApi = createApi({
reducerPath: 'productsApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['Product'],
endpoints: (builder) => ({
getProducts: builder.query<Product[], void>({
query: () => '/products',
providesTags: ['Product'],
}),
updateProduct: builder.mutation<Product, Partial<Product>>({
query: ({ id, ...patch }) => ({
url: `/products/${id}`,
method: 'PATCH',
body: patch,
}),
invalidatesTags: ['Product'],
}),
}),
});
export const { useGetProductsQuery, useUpdateProductMutation } = productsApi;
Redux Toolkit is de juiste keuze wanneer u foutopsporing in de tijd nodig heeft via Redux DevTools, wanneer meerdere teams moeten bijdragen aan een gedeeld statusmodel met afgedwongen conventies, of wanneer u over complexe cross-slice bedrijfslogica beschikt die profiteert van middleware zoals redux-saga of redux-observable.
Zustand: lichtgewicht mondiale staat zonder de ceremonie
Zustand hanteert een radicaal andere aanpak. Er is geen aanbieder, geen reducer en geen actietype. Je definieert een winkel als een eenvoudig JavaScript-object met status en methoden, en gebruikt deze vervolgens met een enkele hook. De gehele API past op één scherm.
// stores/useCartStore.ts
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
totalPrice: () => number;
clear: () => void;
}
export const useCartStore = create<CartStore>()(
devtools(
persist(
(set, get) => ({
items: [],
addItem: (item) =>
set((state) => {
const existing = state.items.find((i) => i.id === item.id);
if (existing) {
return {
items: state.items.map((i) =>
i.id === item.id
? { ...i, quantity: i.quantity + item.quantity }
: i
),
};
}
return { items: [...state.items, item] };
}),
removeItem: (id) =>
set((state) => ({ items: state.items.filter((i) => i.id !== id) })),
totalPrice: () =>
get().items.reduce((sum, i) => sum + i.price * i.quantity, 0),
clear: () => set({ items: [] }),
}),
{ name: 'cart-storage' }
)
)
);
Gedetailleerde abonnementen en prestaties
Zustand lost het re-renderprobleem van Context op via selector-gebaseerde abonnementen. Een component wordt alleen opnieuw weergegeven als het geselecteerde statussegment is gewijzigd.
// Only re-renders when items.length changes, not on price changes
const itemCount = useCartStore((state) => state.items.length);
// Only re-renders when the total changes
const total = useCartStore((state) => state.totalPrice());
Het middleware-ecosysteem van Zustand dekt persistentie localStorage, DevTools-integratie, mutaties in Immers-stijl en URL-synchronisatie. Het is de beste keuze voor teams die mogelijkheden op Redux-niveau willen zonder instelkosten op Redux-niveau.
Jotai: Atomic State geïnspireerd door terugslag
Jotai modelleert toestanden als een grafiek van atomen: kleine, samenstelbare toestandseenheden die van elkaar kunnen worden afgeleid. Dit model is vooral krachtig voor de dynamische status: denk aan een lijst met editorknooppunten waarbij elk knooppunt zijn eigen onafhankelijke selectie-/focusstatus heeft, of een complexe vorm waarbij de zichtbaarheid van het veld afhangt van andere veldwaarden.
// atoms/filterAtoms.ts
import { atom, selector } from 'jotai';
export const searchTermAtom = atom('');
export const categoryAtom = atom<string | null>(null);
export const productsAtom = atom<Product[]>([]);
// Derived atom — recomputes only when its dependencies change
export const filteredProductsAtom = atom((get) => {
const products = get(productsAtom);
const term = get(searchTermAtom).toLowerCase();
const category = get(categoryAtom);
return products.filter((p) => {
const matchesTerm = p.name.toLowerCase().includes(term);
const matchesCategory = category === null || p.category === category;
return matchesTerm && matchesCategory;
});
});
// ProductList.tsx
import { useAtom, useAtomValue } from 'jotai';
function ProductList() {
const [searchTerm, setSearchTerm] = useAtom(searchTermAtom);
const filtered = useAtomValue(filteredProductsAtom);
return (
<>
<input value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />
{filtered.map((p) => <ProductCard key={p.id} product={p} />)}
</>
);
}
Het fijnmazige reactiviteitsmodel van Jotai betekent dat wanneer de zoekterm verandert, alleen componenten worden gebruikt die zich daadwerkelijk abonneren filteredProductsAtom opnieuw renderen. Componenten waarop alleen geabonneerd is categoryAtom zijn onaangeroerd. Dit maakt Jotai uitstekend voor toepassingen met compacte, onderling afhankelijke toestandsgrafieken.
TanStack Query: de juiste thuisbasis voor serverstatus
TanStack Query (voorheen React Query) is geen algemene statusbeheerbibliotheek; het is een serverstatusbibliotheek, en deze gaat beter om met die verantwoordelijkheid dan iets anders in het ecosysteem. Het ophalen, in de cache opslaan, synchroniseren en bijwerken van gegevens op afstand in React zonder TanStack Query betekent doorgaans het handmatig beheren van laadvlaggen, foutobjecten en cache-invalidatie in de lokale staat of in een globaal archief. TanStack Query automatiseert dat allemaal.
// hooks/useProducts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useProducts(filters: ProductFilters) {
return useQuery({
queryKey: ['products', filters],
queryFn: () => api.getProducts(filters),
staleTime: 5 * 60 * 1000, // treat data as fresh for 5 minutes
gcTime: 10 * 60 * 1000, // keep unused data in cache for 10 minutes
});
}
export function useUpdateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (update: ProductUpdate) => api.updateProduct(update),
// Optimistic update
onMutate: async (update) => {
await queryClient.cancelQueries({ queryKey: ['products'] });
const previous = queryClient.getQueryData(['products']);
queryClient.setQueryData(['products'], (old: Product[]) =>
old.map((p) => (p.id === update.id ? { ...p, ...update } : p))
);
return { previous };
},
onError: (_err, _update, context) => {
queryClient.setQueryData(['products'], context?.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['products'] });
},
});
}
De querysleutelarray is de cacheprimitief van TanStack Query. Elke query die dezelfde sleutel deelt, deelt dezelfde cache-invoer. Wanneer u een sleutel ongeldig maakt, wordt elk onderdeel dat op die sleutel is geabonneerd, automatisch opnieuw opgehaald op de achtergrond en bijgewerkt wanneer er nieuwe gegevens binnenkomen.
Prefetching en hydratatie in Next.js
In een Next.js-toepassing kunt u zoekopdrachten vooraf op de server ophalen en deze dehydrateren in de HTML-payload, waardoor laadspinners voor de initiële weergave van de pagina volledig worden geëlimineerd.
// app/products/page.tsx (Next.js App Router)
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
export default async function ProductsPage() {
const queryClient = new QueryClient();
await queryClient.prefetchQuery({
queryKey: ['products', {}],
queryFn: () => api.getProducts({}),
});
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<ProductList />
</HydrationBoundary>
);
}
Bibliotheken combineren: een praktische architectuur
Echte toepassingen profiteren bijna altijd van het combineren van meerdere tools, die elk verantwoordelijk zijn voor hun eigen statuscategorie. Een goed gestructureerde middelgrote tot grote React-applicatie zou er als volgt uit kunnen zien:
- TanStack-query is eigenaar van alle serverstatussen. Het haalt, cachet en synchroniseert externe gegevens. Niets anders raakt de API-reacties.
- Zustand is eigenaar van de globale clientstatus: het winkelwagentje, de geverifieerde gebruikerssessie, UI-voorkeuren en alle coördinatie tussen functies die te complex is voor Context.
- React-context bezit een configuratie met weinig verloop: het huidige thema, de landinstelling en functievlaggen.
- useState/useReducer eigen lokale componentstatus — modaal open/gesloten, formulierveldwaarden (of React Hook Form voor complexe formulieren), tabbladselectie.
- URL-zoekparameters eigen navigatiestatus — actieve filters, sorteervolgorde, huidig paginanummer.
Deze scheiding van belangen is niet alleen esthetisch. Het betekent dat uw serverstatuscache nooit vervuild raakt door de UI-status, dat uw wereldwijde clientarchief nooit overloopt van responsgegevens die TanStack Query efficiënter in de cache zou plaatsen, en dat uw Context nooit app-brede re-renders activeert omdat iemand er een snel veranderende waarde in heeft opgeslagen.
Prestatiepatronen die het waard zijn om te kennen
In het geheugen opgeslagen selectors met Opnieuw selecteren
Wanneer u berekende waarden uit een Redux-winkel afleidt, gebruikt u Opnieuw selecteren om te voorkomen dat u bij elke weergave opnieuw moet berekenen. RTK exporteert opnieuw createSelector van Opnieuw selecteren.
import { createSelector } from '@reduxjs/toolkit';
const selectItems = (state: RootState) => state.cart.items;
export const selectCartSummary = createSelector(selectItems, (items) => ({
count: items.reduce((n, i) => n + i.quantity, 0),
total: items.reduce((sum, i) => sum + i.price * i.quantity, 0),
}));
Renders splitsen met useTransition
React 18's useTransition Hiermee kunt u een statusupdate als niet-dringend markeren, zodat de browser deze kan onderbreken om werk met een hogere prioriteit, zoals gebruikersinvoer, af te handelen. Dit is met name handig wanneer een statuswijziging een dure herweergave veroorzaakt.
const [isPending, startTransition] = useTransition();
const handleFilterChange = (value: string) => {
startTransition(() => {
setFilterValue(value); // expensive downstream re-render is deferrable
});
};
Objectidentiteitsproblemen vermijden
Een veel voorkomende bron van fantoomherweergaven is het maken van nieuwe object- of array-literals binnen de weergave. useMemo en useCallback zijn hier uw hulpmiddelen, maar pas ze alleen toe als u aanwijzingen heeft voor een prestatieprobleem; voortijdige memorisatie voegt cognitieve overhead toe zonder gegarandeerd voordeel.
Beslissingskader
Gebruik de volgende beslissingsboom bij het kiezen van een statusbeheerbenadering voor een nieuwe functie:
- Is de status puur lokaal voor één component of voor een kleine subboom? Gebruik
useStateofuseReducer. - Vertegenwoordigt de status gegevens die zijn opgehaald van een server? Gebruik TanStack-query.
- Is het een mondiale klantstatus die veel componenten in de boom nodig hebben? Reik naar Zustand tenzij je al in een Redux-codebase zit.
- Verandert de status zelden en dient deze een configuratiedoel (thema, locale)? Gebruik React-context.
- Is de staat sterk verbonden met veel afgeleide waarden? Overweeg Jotai.
- Heeft u foutopsporing in de tijd nodig, strikte architecturale conventies of complexe asynchrone sagen? Gebruik Redux-toolkit.
Veelvoorkomende antipatronen die u moet vermijden
- Serverreacties opslaan in Redux of Zustand. Laat TanStack Query die gegevens bezitten. Het dupliceren ervan in een globale winkel veroorzaakt synchronisatiebugs.
- Alles onderbrengen in één grote winkel. Een niet-gerelateerde status die tot dezelfde slice behoort, dwingt tot onnodige koppeling en maakt het testen moeilijker.
- Context gebruiken voor hoogfrequente updates. Elke verandering in contextwaarde brengt alle consumenten opnieuw tot leven. Voor waarden die bij elke toetsaanslag of animatieframe veranderen, is Context het verkeerde hulpmiddel.
- Afleidende staat binnen componenten. Berekende waarden die afhankelijk zijn van de status moeten in het geheugen opgeslagen selectors of afgeleide atomen zijn, en geen inline berekeningen die bij elke weergave worden uitgevoerd.
- Het negeren van laad- en foutstatussen. De serverstatus is inherent asynchroon. TanStack Query-oppervlakken
isLoading,isError, enerrorvoor elke vraag; gebruik ze.
Conclusie
Het staatsmanagementlandschap in React is nog nooit zo gezond geweest. Je hoeft niet langer te kiezen tussen een zwaar raamwerk en alles vanaf nul opbouwen. Redux Toolkit levert voorspelbaarheid op ondernemingsniveau zonder de standaardtekst van zijn voorganger. Zustand brengt diezelfde kracht met een fractie van de opstelling. Jotai lost de moeilijke problemen op in fijnkorrelige, dynamische toestandsgrafieken. En TanStack Query heeft de serverstatus definitief opgelost – als je nog steeds API-reacties beheert useEffect en useState, je bent het aan jezelf verplicht om te migreren.
Het belangrijkste inzicht is categorisch: stel eerst vast met wat voor soort toestand u te maken heeft en selecteer vervolgens de tool die voor die categorie is geoptimaliseerd. Weersta het instinct om alles in één systeem te gieten. Een gelaagde aanpak – TanStack Query voor de serverstatus, Zustand voor de globale clientstatus, Context voor configuratie en lokale status voor al het andere – levert codebases op waarover gemakkelijker te redeneren is, gemakkelijker te testen en veel beter onderhoudbaar naarmate teams en vereisten groeien.