Skip to main content

Implementing State Management Libraries

1. Setting Up Redux Toolkit

Example: Basic Redux Toolkit Setup

// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Configuration Option Description Default Value
reducer Root reducer function or object mapping slice names to reducers null
middleware Middleware to add to the store Pre-configured middleware
devTools Enable Redux DevTools extension true
preloadedState Initial state for the store undefined
Note: Redux Toolkit automatically includes the Redux DevTools extension and pre-configured middleware.

Example: Slice Creation

// features/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

Setup Steps

  • Install @reduxjs/toolkit and react-redux
  • Create a store using configureStore
  • Define slices with reducers using createSlice
  • Provide the store to your app via <Provider>

2. Creating Slices

Property Description Example
name Name of the slice "user"
initialState Initial state for the slice { id: null, name: "", email: "" }
reducers Functions that define how the state changes { setUserInfo: (state, action) => { ... } }

Example: Creating a Slice

import { createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'user',
  initialState: {
    id: null,
    name: '',
    email: ''
  },
  reducers: {
    setUserInfo: (state, action) => {
      state.id = action.payload.id;
      state.name = action.payload.name;
      state.email = action.payload.email;
    },
    clearUser: (state) => {
      state.id = null;
      state.name = '';
      state.email = '';
    }
  }
});

export const { setUserInfo, clearUser } = userSlice.actions;
export default userSlice.reducer;
Note: Slices are created using createSlice which automatically generates action creators and action types based on the reducer names.

3. Using useSelector and useDispatch

Hook Description Usage
useSelector Extracts data from the Redux store state const value = useSelector(state => state.key)
useDispatch Returns a reference to the dispatch function from the Redux store const dispatch = useDispatch()

Example: Basic usage of useSelector and useDispatch

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';

const Counter = () => {
  const count = useSelector(state => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <span>{count}</span>
      <button onClick={() => dispatch({ type: 'counter/increment' })}>
        Increment
      </button>
    </div>
  );
};

export default Counter;
Note: useSelector is optimized to avoid unnecessary re-renders. It only re-renders when the selected value has changed.
Warning: Avoid inline functions in useSelector as they can cause unnecessary re-renders. Prefer memoized selectors.

Key Points

  • useSelector is a React hook that allows you to extract data from the Redux store state.
  • useDispatch is a React hook that returns the Redux store's dispatch function.
  • These hooks simplify integration of Redux with React components.

4. Implementing Async Actions

Action Type Description Example
FETCH_DATA_START Indicates the start of an async data fetch operation dispatch({ type: 'FETCH_DATA_START' });
FETCH_DATA_SUCCESS Indicates successful completion of async data fetch dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
FETCH_DATA_FAILURE Indicates failure during async data fetch dispatch({ type: 'FETCH_DATA_FAILURE', payload: error });

Example: Using Redux with Async Actions

import { createAction, createReducer } from '@reduxjs/toolkit';

// Async action creators
const fetchData = createAsyncThunk(
  'data/fetch',
  async () => {
    const response = await fetch('/api/data');
    return response.json();
  }
);

// Reducer handling async actions
const dataReducer = createReducer(
  { loading: false, data: null, error: null },
  {
    [fetchData.pending]: (state) => {
      state.loading = true;
    },
    [fetchData.fulfilled]: (state, action) => {
      state.loading = false;
      state.data = action.payload;
    },
    [fetchData.rejected]: (state, action) => {
      state.loading = false;
      state.error = action.error.message;
    }
  }
);

Example: Using React Context with Async Actions

const DataContext = createContext();

export const DataProvider = ({ children }) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);

  const fetchData = async () => {
    setLoading(true);
    try {
      const response = await fetch('/api/data');
      const result = await response.json();
      setData(result);
    } catch (error) {
      console.error('Fetch failed:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <DataContext.Provider value={{ data, loading, fetchData }}>
      {children}
    </DataContext.Provider>
  );
};
Note: When implementing async actions in React Native, ensure proper error handling and loading states to provide a smooth user experience.

5. Using Zustand for Lightweight State

Example: Basic Store Setup

import { create } from 'zustand'

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}))

export default useStore
Property Description Usage
create Function to create a new store const useStore = create((set) => ...)
set Function to update store state set((state) => ({ count: state.count + 1 }))
get Function to read store state (optional) get().count
Note: Zustand is a lightweight state management solution that works well with React Native's component model.

Example: Async Actions

import { create } from 'zustand'

const useStore = create((set) => ({
  data: null,
  loading: false,
  fetchData: async () => {
    set({ loading: true })
    try {
      const response = await fetch('https://api.example.com/data')
      const result = await response.json()
      set({ data: result, loading: false })
    } catch (error) {
      set({ loading: false })
    }
  },
}))

export default useStore

Key Features of Zustand

  • Minimal API surface
  • No provider needed
  • Supports middleware and plugins
  • Works with React Native's async patterns

6. Using Jotai for Atomic State

Example: Basic Jotai Setup

// Define an atom
import { atom, useAtom } from 'jotai'

const countAtom = atom(0)

// Use in a component
function Counter() {
  const [count, setCount] = useAtom(countAtom)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
Feature Description Notes
atom() atom(initialValue) Creates a new atom with an initial value
useAtom() useAtom(atom) Hook to read and write atom values
useAtomValue() useAtomValue(atom) Hook to read atom value only
useSetAtom() useSetAtom(atom) Hook to set atom value only
Note: Jotai atoms are immutable, and updates create new atom instances.

Example: Async Atom

import { atom } from 'jotai'
import { atomWithCache } from 'jotai/cache'

const fetchUser = async (id) => {
  const res = await fetch(`/api/users/${id}`)
  return res.json()
}

const userAtom = atomWithCache(async (get, id) => {
  return await fetchUser(id)
})

Example: Derived State

import { atom } from 'jotai'

const countAtom = atom(0)
const doubledAtom = atom(get => get(countAtom) * 2)

function DoubledCounter() {
  const [doubled] = useAtom(doubledAtom)
  return <div>Doubled: {doubled}</div>
}

Key Features of Jotai

  • Atomic state management with fine-grained reactivity
  • Supports async atoms and caching
  • Minimal API surface with powerful composition
  • Works well with React's concurrent rendering

7. Implementing Persist Middleware

Feature Description Implementation
persistKey Unique key to identify the persisted state 'userState'
storage Storage mechanism (e.g., AsyncStorage, Redux Persist) AsyncStorage
whitelist State keys to persist (optional) ['user', 'theme']
blacklist State keys to exclude from persistence (optional) ['loading', 'errors']

Example: Redux Persist Setup

import { persistStore, persistReducer } from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  whitelist: ['user', 'theme'],
};

const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
  reducer: persistedReducer,
});

export const persistor = persistStore(store);

Example: Using with React Navigation

import { NavigationContainer } from '@react-navigation/native';
import { PersistGate } from 'redux-persist/integration/react';

function App() {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <NavigationContainer>
          <AppNavigator />
        </NavigationContainer>
      </PersistGate>
    </Provider>
  );
}
Note: Ensure AsyncStorage is properly configured in your project for persistence to work.
Warning: Large state objects can cause performance issues when persisted. Consider using whitelist to limit stored keys.

8. Using Recoil for React State

Example: Basic Recoil Setup

import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';

// Define an atom (state)
const countAtom = atom({
  key: 'count',
  default: 0
});

// Define a selector (computed value)
const doubleCountSelector = selector({
  key: 'doubleCount',
  get: ({ get }) => {
    const count = get(countAtom);
    return count * 2;
  }
});

// Component using the state
function Counter() {
  const [count, setCount] = useRecoilState(countAtom);
  const doubleCount = useRecoilValue(doubleCountSelector);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {doubleCount}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}
Feature Description Notes
atom atom({ key, default }) Defines a piece of state. key must be unique across the app.
selector selector({ key, get }) Computes derived state from atoms or other selectors.
useRecoilState useRecoilState(atom) Returns [value, setter] for reading and updating atom.
useRecoilValue useRecoilValue(atomOrSelector) Returns the value of an atom or selector (read-only).
Note: Recoil is a state management library built by Facebook, designed to work with React components.
Warning: Ensure all key values in atoms and selectors are unique to prevent state conflicts.

Key Concepts in Recoil

  • Atoms represent the source of truth for state.
  • Selectors derive state from atoms or other selectors.
  • useRecoilState provides read-write access to atoms.
  • useRecoilValue provides read-only access to atoms or selectors.

9. Managing Global UI State

Library Core Features UI State Management Notes
Redux Centralized store, actions, reducers useSelector, useDispatch Requires react-redux bindings
Zustand Simple API, middleware support useStore, create Lightweight alternative to Redux
Jotai Atom-based state management useAtom, atom Granular state updates with atomic units
Recoil Atoms, Selectors, persistent state useRecoilValue, useSetRecoilState Developed by Facebook, supports async state

Example: Zustand UI State Management

import { create } from 'zustand';\n\nconst useUIStore = create((set) => ({\n  isModalOpen: false,\n  toggleModal: () => set((state) => ({ isModalOpen: !state.isModalOpen })),\n}));\n\nfunction ModalComponent() {\n  const { isModalOpen, toggleModal } = useUIStore();\n  \n  return (\n    <div>\n      <button onClick={toggleModal}>Toggle Modal</button>\n      {isModalOpen && (\n        <div className="modal">\n          <h2>Modal Content</h2>\n        </div>\n      )}\n    </div>\n  );\n}

Example: Jotai UI State Management

import { atom, useAtom } from 'jotai';\n\nconst modalOpenAtom = atom(false);\n\nfunction ModalComponent() {\n  const [isModalOpen, setIsModalOpen] = useAtom(modalOpenAtom);\n\n  return (\n    <div>\n      <button onClick={() => setIsModalOpen(!isModalOpen)}>Toggle Modal</button>\n      {isModalOpen && (\n        <div className="modal">\n          <h2>Modal Content</h2>\n        </div>\n      )}\n    </div>\n  );\n}
Note: For global UI state like modals, toasts, or navigation, consider using lightweight libraries such as Zustand or Jotai for simpler setup and better performance compared to Redux.

10. Choosing State Management Approach

State Management Options in React Native

  • Redux: Centralized store with predictable state updates; requires more boilerplate and setup.
  • Context API: Built-in React feature for sharing state across components without prop drilling; suitable for small to medium apps.
  • Recoil: Atom-based state management by Facebook; offers fine-grained reactivity and is more flexible than Context API.
  • Jotai: Minimalist, atom-based state management; easy to use and highly composable.
  • Zustand: Lightweight, simple store with minimal boilerplate; supports middleware and async operations.
  • Pinia: Modern, Vue-inspired state management; offers a more intuitive API and better TypeScript support.
Feature Redux Context API Recoil Jotai Zustand Pinia
Centralized Store true false true false true true
Reactivity Manual Full Fine-grained Fine-grained Manual Automatic
Boilerplate High Low Medium Low Very Low Low
TypeScript Support Good Good Excellent Excellent Good Excellent
Middleware Support Yes No No No Yes Yes

Decision Factors

  • App Size: For small to medium apps, Context API or Jotai may suffice. Larger apps might benefit from Redux or Zustand.
  • Team Experience: Teams familiar with Redux might prefer it; newer teams may find Zustand or Jotai easier to adopt.
  • Reactivity Needs: For fine-grained reactivity, Recoil or Jotai are preferred.
  • Tooling: Redux has mature tooling like Redux DevTools, while Zustand and Pinia have modern alternatives.

Was this article helpful?