State management is one of the most debated topics in React Native. The answer has shifted considerably in the last two years: Context API scaled further than people expected, Redux lost ground to lighter alternatives, and Zustand and Jotai emerged as genuine production options. Here's how I evaluate each for mobile projects today, with real code to back it up.
The Landscape in 2025
Three tools dominate new project decisions: Redux Toolkit (the modern Redux experience), Zustand (lightweight, store-based), and Jotai (atomic, bottom-up). All three are actively maintained and work in React Native without modification. The choice is rarely about capability — it's about team mental model and app complexity.
Redux Toolkit
Redux Toolkit (RTK) is Redux without the boilerplate. createSlice generates actions and reducers together, and createAsyncThunk handles async flows cleanly.
import { createSlice, createAsyncThunk, configureStore } from '@reduxjs/toolkit';
export const fetchUser = createAsyncThunk('user/fetch', async (userId) => {
const response = await api.getUser(userId);
return response.data;
});
const userSlice = createSlice({
name: 'user',
initialState: { data: null, loading: false, error: null },
reducers: {
clearUser: (state) => { state.data = null; },
},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => { state.loading = true; })
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
export const store = configureStore({
reducer: { user: userSlice.reducer },
});
Use RTK when: you have a large team, complex async flows, need Redux DevTools time-travel debugging, or are maintaining an existing Redux codebase.
Zustand
Zustand is a single-store solution with a minimal API. The entire store and actions live in one create call, and components only re-render when the specific slice they consume changes:
import { create } from 'zustand';
const useAuthStore = create((set, get) => ({
user: null,
isAuthenticated: false,
login: async (credentials) => {
const user = await api.login(credentials);
set({ user, isAuthenticated: true });
},
logout: () => {
set({ user: null, isAuthenticated: false });
api.clearSession();
},
getToken: () => get().user?.token,
}));
// In a component
function ProfileScreen() {
const { user, logout } = useAuthStore();
return <Button title="Sign out" onPress={logout} />;
}
Use Zustand when: you want a Redux-like model without the ceremony, or for small-to-medium apps where RTK feels heavyweight.
Jotai
Jotai takes the atomic approach: state lives in small, composable atoms rather than a single store. Derived state is computed lazily and cached automatically:
import { atom, useAtom, useAtomValue } from 'jotai';
const userAtom = atom(null);
const themeAtom = atom('dark');
// Derived atom — recomputes only when userAtom changes
const displayNameAtom = atom((get) => {
const user = get(userAtom);
return user ? `${user.firstName} ${user.lastName}` : 'Guest';
});
function Header() {
const displayName = useAtomValue(displayNameAtom);
return <Text>Welcome, {displayName}</Text>;
}
Use Jotai when: your state is naturally granular — UI state, form fields, feature flags — or you're coming from a Recoil background.
Bundle Size Comparison
- Zustand: ~1.2 KB minified + gzipped
- Jotai: ~3 KB minified + gzipped
- Redux Toolkit (core): ~11 KB minified + gzipped (includes Immer and Redux)
For mobile, bundle size affects startup time. On low-end Android devices, every additional KB matters during the initial JS parse phase. Zustand has a meaningful advantage here.
Persistence Strategies
All three libraries persist well with @react-native-async-storage/async-storage.
For Redux Toolkit, use redux-persist:
import { persistStore, persistReducer } from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['auth'],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
For Zustand, use the built-in persist middleware:
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
const useAuthStore = create(
persist(
(set) => ({ user: null, login: (user) => set({ user }) }),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
When to Use Which
- Redux Toolkit: Large teams, complex async workflows, existing Redux codebase, need for DevTools.
- Zustand: Medium apps, teams that find Redux verbose, apps with a handful of global state domains.
- Jotai: Highly granular state, lots of derived/computed values, smaller apps or feature-level state isolation.
My Take
For most new React Native apps in 2025, I reach for Zustand first. It's small, the mental model is simple enough to onboard a new developer in minutes, and it scales to medium complexity without friction. I introduce RTK when the team is large enough that Redux's structural conventions become a feature rather than a constraint. Jotai earns its place in apps with genuinely atomic state — think a complex form builder or a real-time collaborative tool where individual fields have independent subscription semantics.

