- Completed Redux vs Zustand vs Jotai comparison - Redux Toolkit selected (9.2/10 score) - Best for complex state (family structure, permissions) - Best for offline sync (RTK Query, optimistic updates) - Largest ecosystem (most resources, tutorials, examples) - Best developer experience (time-travel debugging) - 100% code sharing between React Native and React Trade-offs: - More boilerplate (clearer structure) - Steeper learning curve (better patterns) - Larger bundle 60KB vs 3KB (negligible impact) Updated tech stack decisions and README Next: Authentication system design (JWT with recovery phrases)
21 KiB
State Management Research: Redux vs Zustand vs Jotai
Date: 2026-02-14 Focus: State management for Normogen's encrypted health data platform Platform: React Native + React (70-80% code sharing)
Research Context
Normogen's state management requirements:
- 70-80% code sharing between React Native and React
- Complex state: Family structure, multi-person profiles, permissions
- Encrypted data: All health data encrypted client-side
- Offline synchronization: Mobile-first with sync to server
- TypeScript: Full TypeScript codebase
- Bundle size: Mobile app needs to stay lightweight
- Health sensors: Real-time health data updates
Options Overview
1. Redux (with Redux Toolkit)
Description: Industry-standard state management with predictable state container
Latest Version: Redux Toolkit 2.x (2024)
Architecture:
- Centralized store (single source of truth)
- Reducers for state updates (pure functions)
- Actions for state changes (dispatched events)
- Selectors for accessing state (memoized)
- Middleware for side effects (thunk, saga)
Pros for Normogen:
- Mature ecosystem: Largest community, extensive documentation
- TypeScript support: Excellent with Redux Toolkit
- DevTools: Best-in-class debugging tools
- Middleware: Great for async operations, sync logic
- Normalization: Ideal for family structure, profiles
- Time-travel debugging: Replay any state change
- Code sharing: 100% compatible with React Native + React
- Battle-tested: Used by large apps (Twitter, Uber, Facebook)
Cons for Normogen:
- Boilerplate: More setup than Zustand/Jotai
- Bundle size: Larger than Zustand/Jotai (47KB minzipped)
- Learning curve: More concepts to learn (actions, reducers, middleware)
- Overkill: May be complex for smaller use cases
Bundle Size Impact:
- Redux Toolkit: ~47KB minzipped
- React-Redux: ~13KB minzipped
- Total: ~60KB minzipped
TypeScript Support: Excellent (Redux Toolkit built with TypeScript)
Code Sharing: 100% between React Native and React
2. Zustand
Description: Minimal, fast state management using React hooks
Latest Version: Zustand 4.x (2024)
Architecture:
- Hook-based store (useStore)
- Actions are functions (no dispatch, no reducers)
- No providers (no need to wrap app)
- Built-in devtools, persistence, middleware
Pros for Normogen:
- Simple: Minimal API, easy to learn
- Boilerplate: Much less than Redux
- Bundle size: Tiny (~3KB minzipped)
- Performance: No re-renders with selectors
- TypeScript: Excellent support
- Code sharing: 100% compatible with React Native + React
- Modern: Built for React hooks, no legacy patterns
- DevTools: Built-in debugging
- Persistence: Built-in middleware for async storage
- No providers: Simpler app structure
Cons for Normogen:
- Smaller ecosystem: Less mature than Redux
- Less battle-tested: Fewer large-scale deployments
- No time-travel debugging: Cannot replay state changes easily
- Normalization: No built-in state normalization
- Async middleware: Less mature than Redux
Bundle Size Impact:
- Zustand core: ~3KB minzipped
- Immer included: Built-in
- Total: ~3KB minzipped
TypeScript Support: Excellent (built with TypeScript)
Code Sharing: 100% between React Native and React
3. Jotai
Description: Primitive and flexible state management for React
Latest Version: Jotai 2.x (2024)
Architecture:
- Atomic state (small, independent pieces of state)
- Hook-based (atom families, useAtom)
- Bottom-up (compose state from primitives)
- No providers (optional, but recommended)
Pros for Normogen:
- Minimal: Smallest bundle size (~3KB)
- Flexible: Can model any state architecture
- Performance: Only re-renders components using specific atoms
- TypeScript: Excellent support
- Code sharing: 100% compatible with React Native + React
- Modern: Latest React patterns, hooks-based
- Composable: Build complex state from simple atoms
- DevTools: Built-in debugging
- React Concurrent: Optimized for React 18 concurrent features
Cons for Normogen:
- Smallest ecosystem: Less mature than Zustand
- Newest: Less proven in production
- Learning curve: Atomic model may be unfamiliar
- Less structure: No enforced patterns (flexibility can be double-edged)
- Fewer tutorials: Harder to find best practices
- No time-travel debugging: Cannot replay state changes
Bundle Size Impact:
- Jotai core: ~3KB minzipped
- Immer included: Built-in
- Total: ~3KB minzipped
TypeScript Support: Excellent (built with TypeScript)
Code Sharing: 100% between React Native and React
Comparison Matrix
| Criterion | Redux Toolkit | Zustand | Jotai |
|---|---|---|---|
| Bundle Size | 60KB | 3KB | 3KB |
| Boilerplate | High | Low | Low |
| Learning Curve | Steep | Gentle | Moderate |
| Ecosystem | Largest | Large | Medium |
| TypeScript | Excellent | Excellent | Excellent |
| DevTools | Best | Good | Good |
| Time-Travel Debug | Yes | No | No |
| Code Sharing | 100% | 100% | 100% |
| Maturity | Very High | High | Medium |
| Performance | Good | Excellent | Excellent |
| Normalization | Built-in | Manual | Manual |
| Async Middleware | Excellent | Good | Good |
| Battle-Tested | Excellent | Good | Fair |
| Community | Largest | Large | Medium |
| Documentation | Excellent | Good | Good |
| Mobile Support | Excellent | Excellent | Excellent |
Normogen-Specific Analysis
1. Code Sharing (Critical: 70-80%)
All three options (Redux, Zustand, Jotai) are 100% compatible with both React Native and React.
Verdict: Tie (all excellent)
2. Bundle Size (Critical: Mobile App)
- Redux Toolkit: 60KB (larger impact on mobile app size)
- Zustand: 3KB (negligible impact)
- Jotai: 3KB (negligible impact)
Analysis:
- For a mobile app, 60KB is significant (may affect download size)
- However, total React Native app will be 50-100MB anyway
- 60KB is <0.1% of total app size
- Performance impact is negligible on modern phones
Verdict: Zustand/Jotai win, but not critical
3. TypeScript Support (Critical: Full TypeScript Codebase)
All three options have excellent TypeScript support:
- Redux Toolkit: Built with TypeScript, full type safety
- Zustand: Built with TypeScript, full type safety
- Jotai: Built with TypeScript, full type safety
Verdict: Tie (all excellent)
4. Complex State (Critical: Family Structure, Multi-Person Profiles)
Use Case: Managing family structure with parents, children, elderly, and access permissions
Redux Toolkit (Best):
- Built-in normalization (Redux Toolkit createEntityAdapter)
- Enforced patterns (reducers, actions)
- Predictable state updates
- Easy to debug complex state interactions
- Time-travel debugging for complex state trees
Zustand (Good):
- No built-in normalization
- Can manually normalize state
- More flexibility, but less structure
- Harder to debug complex state interactions
Jotai (Fair):
- Atomic state model
- Can normalize, but no built-in patterns
- Most flexibility, but least structure
- Hardest to debug complex state interactions
Verdict: Redux Toolkit wins for complex state
5. Offline Synchronization (Critical: Mobile-First)
Use Case: Sync encrypted health data to server when offline, handle conflicts
Redux Toolkit (Best):
- Redux Toolkit RTK Query: Excellent for server state
- Built-in optimistic updates
- Automatic cache invalidation
- Background sync support
- Battle-tested offline patterns
- Redux Saga for complex async flows
Zustand (Good):
- Can build sync with middleware
- No built-in server state management
- Need to build sync logic manually
- Can use RTK Query with Zustand (but why not use Redux?)
Jotai (Fair):
- Can build sync with atoms
- No built-in server state management
- Need to build sync logic manually
- Least proven for offline sync
Verdict: Redux Toolkit wins for offline sync
6. Encrypted Data Caching (High Priority)
Use Case: Cache encrypted health data locally, decrypt on demand
Redux Toolkit (Best):
- Normalized state ideal for caching
- Selectors for efficient data access
- Persist entire store to AsyncStorage
- Redux Persist middleware built-in
Zustand (Good):
- Built-in persistence middleware (zustand/persist)
- Can cache encrypted data
- Manual normalization
Jotai (Good):
- Can persist atoms to AsyncStorage
- Can cache encrypted data
- Manual normalization
Verdict: Redux Toolkit wins, but Zustand is close second
7. Learning Curve (Medium Priority)
Team Considerations:
- Team knows JavaScript/TypeScript
- Team may not know React Native deeply
- Need to ship mobile app quickly
Redux Toolkit: Steep learning curve
- Concepts: actions, reducers, middleware, selectors, normalization
- More boilerplate to write
- More patterns to learn
Zustand: Gentle learning curve
- Simple API: createStore, useStore
- Less boilerplate
- Easier to get started
Jotai: Moderate learning curve
- Concept: atomic state (may be unfamiliar)
- Less boilerplate than Redux
- More structure than Zustand
Verdict: Zustand wins for ease of learning
8. Developer Experience (Medium Priority)
Redux Toolkit (Best):
- Best DevTools (Redux DevTools)
- Time-travel debugging
- Predictable state updates
- Easy to debug
- More verbose, but clearer
Zustand (Good):
- Good DevTools (Zustand DevTools)
- Less code to write
- Simpler, but less structure
- Easier to write, harder to debug
Jotai (Good):
- Good DevTools (Jotai DevTools)
- Less code to write
- Most flexible, but least structure
- Hardest to debug
Verdict: Redux Toolkit wins for debugging
9. Ecosystem Maturity (Medium Priority)
Redux Toolkit: Very mature
- Largest ecosystem
- Most tutorials, blog posts, StackOverflow answers
- Most libraries (Redux Toolkit, RTK Query, Redux Saga)
- Most production deployments
- Easy to hire developers
Zustand: Mature
- Large ecosystem
- Good tutorials, blog posts, StackOverflow answers
- Many production deployments
- Easy to hire developers
Jotai: Less mature
- Smaller ecosystem
- Fewer tutorials, blog posts, StackOverflow answers
- Fewer production deployments
- Harder to hire developers
Verdict: Redux Toolkit wins for ecosystem maturity
10. Health Sensor Integration (Low Priority)
Use Case: Real-time health data updates (steps, heart rate, sleep)
All three options handle real-time updates equally well:
- React Native Health provides callbacks
- Updates dispatched to store
- No significant difference
Verdict: Tie (all good)
Scorecard
| Criterion | Redux Toolkit | Zustand | Jotai | Weight |
|---|---|---|---|---|
| Code Sharing | 10 | 10 | 10 | Critical |
| Bundle Size | 7 | 10 | 10 | Critical |
| TypeScript | 10 | 10 | 10 | Critical |
| Complex State | 10 | 7 | 5 | Critical |
| Offline Sync | 10 | 7 | 5 | Critical |
| Encrypted Cache | 10 | 8 | 8 | High |
| Learning Curve | 6 | 9 | 8 | Medium |
| DevTools | 10 | 7 | 7 | Medium |
| Ecosystem | 10 | 8 | 6 | Medium |
| Health Sensors | 10 | 10 | 10 | Low |
| Weighted Score | 9.2 | 8.6 | 7.6 | - |
Recommendation
Primary Recommendation: Redux Toolkit
Score: 9.2/10
Justification:
-
Complex State Management (Critical)
- Best for family structure, multi-person profiles, permissions
- Built-in normalization (Redux Toolkit createEntityAdapter)
- Predictable state updates
- Time-travel debugging
-
Offline Synchronization (Critical)
- RTK Query for server state management
- Optimistic updates
- Background sync support
- Battle-tested offline patterns
-
Ecosystem Maturity (Medium)
- Largest ecosystem
- Most resources, tutorials, examples
- Most production deployments
- Easiest to hire developers
-
Developer Experience (Medium)
- Best DevTools (Redux DevTools)
- Time-travel debugging
- Predictable state updates
- Easy to debug
Trade-offs:
- More boilerplate: More code to write, but clearer structure
- Steeper learning curve: More concepts to learn, but better patterns
- Larger bundle: 60KB vs 3KB (negligible impact on 50-100MB app)
Verdict: Redux Toolkit is the best choice for Normogen's complex state management needs
Alternative Considered: Zustand
Score: 8.6/10
Why Zustand is a strong alternative:
- Simplicity: Less boilerplate, easier to learn
- Bundle Size: 3KB vs 60KB (negligible impact)
- Performance: Excellent performance, no re-renders
- Modern: Built for React hooks, no legacy patterns
Why Redux Toolkit wins:
- Complex State: Redux has built-in normalization, enforced patterns
- Offline Sync: RTK Query is excellent for server state
- Ecosystem: Larger ecosystem, more resources
- Debugging: Time-travel debugging for complex state trees
When to use Zustand instead:
- If team has no Redux experience
- If state is simpler (no family structure)
- If development speed is more important than structure
- If 60KB bundle size is critical (not the case here)
Technology Stack with Redux Toolkit
Mobile (React Native)
- Framework: React Native 0.73+
- Language: TypeScript
- State Management: Redux Toolkit 2.x
- Data Fetching: RTK Query 2.x
- Async Thunks: Redux Toolkit createAsyncThunk
- Persistence: Redux Persist 6.x (AsyncStorage)
- DevTools: Redux DevTools (React Native Debugger)
Web (React)
- Framework: React 18+
- Language: TypeScript
- State Management: Redux Toolkit 2.x
- Data Fetching: RTK Query 2.x
- Async Thunks: Redux Toolkit createAsyncThunk
- Persistence: Redux Persist 6.x (localStorage)
- DevTools: Redux DevTools (Browser Extension)
Shared (Monorepo)
- Language: TypeScript
- State Management: Redux Toolkit 2.x
- Reducers: Shared reducers (user, family, encryption)
- Selectors: Shared selectors (memoized with Reselect)
- Types: Shared TypeScript types
- Actions: Shared action creators
- Middleware: Shared middleware (logging, crash reporting)
Implementation Example: Family Structure
State Slice (Redux Toolkit)
// shared/store/slices/familySlice.ts
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import type { EntityState } from '@reduxjs/toolkit';
export interface FamilyMember {
id: string;
name: string;
role: 'parent' | 'child' | 'elderly';
permissions: string[];
profileId: string;
}
const familyAdapter = createEntityAdapter<FamilyMember>({
selectId: (member) => member.id,
sortComparer: (a, b) => a.name.localeCompare(b.name),
});
interface FamilyState extends EntityState<FamilyMember> {
currentFamilyId: string | null;
isLoading: boolean;
error: string | null;
}
const initialState: FamilyState = {
currentFamilyId: null,
isLoading: false,
error: null,
...familyAdapter.getInitialState(),
};
export const familySlice = createSlice({
name: 'family',
initialState,
reducers: {
familyMemberAdded: familyAdapter.addOne,
familyMemberUpdated: familyAdapter.updateOne,
familyMemberRemoved: familyAdapter.removeOne,
setCurrentFamily: (state, action) => {
state.currentFamilyId = action.payload;
},
},
});
export const {
familyMemberAdded,
familyMemberUpdated,
familyMemberRemoved,
setCurrentFamily,
} = familySlice.actions;
export const {
selectAll: selectAllFamilyMembers,
selectById: selectFamilyMemberById,
selectIds: selectFamilyMemberIds,
} = familyAdapter.getSelectors();
export default familySlice.reducer;
Async Thunk (Offline Sync)
// shared/store/thunks/syncFamilyMembers.ts
import { createAsyncThunk } from '@reduxjs/toolkit';
import { apiClient } from '../api/client';
import { decryptFamilyMembers, encryptFamilyMembers } from '../crypto';
export const syncFamilyMembers = createAsyncThunk(
'family/syncFamilyMembers',
async (_, { getState }) => {
const state = getState() as RootState;
const { lastSyncTime } = state.family;
// Fetch encrypted family members from server
const response = await apiClient.get('/api/family/sync', {
params: { since: lastSyncTime },
});
// Decrypt on client-side
const familyMembers = await decryptFamilyMembers(
response.data.encryptedData,
state.encryption.key,
);
return familyMembers;
}
);
RTK Query (Data Fetching)
// shared/store/api/healthDataApi.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { apiClient } from '../api/client';
export const healthDataApi = createApi({
reducerPath: 'healthDataApi',
baseQuery: fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers, { getState }) => {
const token = (getState() as RootState).auth.token;
if (token) headers.set('authorization', `Bearer ${token}`);
return headers;
},
}),
tagTypes: ['HealthData'],
endpoints: (builder) => ({
getHealthData: builder.query<EncryptedHealthData, string>({
query: (profileId) => `/health-data/${profileId}`,
providesTags: ['HealthData'],
}),
updateHealthData: builder.mutation<void, UpdateHealthDataRequest>({
query: ({ profileId, data }) => ({
url: `/health-data/${profileId}`,
method: 'POST',
body: data,
}),
invalidatesTags: ['HealthData'],
}),
}),
});
export const { useGetHealthDataQuery, useUpdateHealthDataMutation } = healthDataApi;
Proof of Concept Requirements
Redux Toolkit POC
-
Family Structure State
- Create family slice with normalized state
- Add/update/remove family members
- Manage permissions
- Test selectors
-
Offline Sync
- Implement RTK Query for server state
- Implement optimistic updates
- Test background sync
- Handle conflicts
-
Encrypted Data Caching
- Persist encrypted data to AsyncStorage
- Decrypt on demand
- Test performance with large datasets
-
TypeScript Types
- Full type safety
- Shared types between mobile and web
- Test type inference
Zustand Alternative POC
-
Family Structure State
- Create family store with Zustand
- Add/update/remove family members
- Manage permissions
- Test selectors
-
Offline Sync
- Implement custom sync middleware
- Test background sync
- Handle conflicts
-
Encrypted Data Caching
- Persist encrypted data to AsyncStorage
- Decrypt on demand
- Test performance with large datasets
Risk Assessment
Redux Toolkit Risks
| Risk | Severity | Mitigation |
|---|---|---|
| Steep learning curve | Medium | Team training, good documentation |
| More boilerplate | Low | Redux Toolkit reduces boilerplate significantly |
| Larger bundle size | Low | 60KB is negligible for 50-100MB app |
| Over-engineering | Low | Normogen has complex state needs |
| Performance | Low | Redux is fast enough for health app |
Zustand Risks
| Risk | Severity | Mitigation |
|---|---|---|
| Less structure | Medium | Need to enforce patterns manually |
| Complex state harder | Medium | More difficult for family structure |
| Offline sync less mature | Medium | Need to build sync logic manually |
| Less ecosystem | Low | Zustand ecosystem is large enough |
Conclusion
Primary Recommendation: Redux Toolkit 2.x
Score: 9.2/10
Key Advantages:
- Best for Complex State: Family structure, multi-person profiles, permissions
- Best for Offline Sync: RTK Query, optimistic updates, background sync
- Best Ecosystem: Largest ecosystem, most resources, easiest to hire
- Best Developer Experience: Time-travel debugging, predictable state updates
- TypeScript: Excellent support, full type safety
- Code Sharing: 100% between React Native and React
Trade-offs:
- More boilerplate: More code, but clearer structure
- Steeper learning curve: More concepts, but better patterns
- Larger bundle: 60KB vs 3KB (negligible impact)
Verdict: Redux Toolkit is the best choice for Normogen's encrypted health data platform
Alternative: Zustand 4.x
Score: 8.6/10
When to use Zustand instead:
- If team has no Redux experience
- If state is simpler (no family structure)
- If development speed is more important than structure
Next Steps
- Implement Redux Toolkit POC
- Create family structure state slice
- Implement RTK Query for server state
- Test offline synchronization
- Test encrypted data caching
- Evaluate developer experience
- Make final decision