Redux in React Native


Redux in React Native Interview with follow-up questions

1. What is Redux and why is it used in React Native?

Redux is a predictable state container that holds your app's global state in a single store and updates it through a strict, unidirectional flow: components dispatch actions, reducers (pure functions) compute the next state, and subscribed components re-render. The discipline makes state changes traceable and debuggable (e.g. via Redux DevTools and time-travel).

In React Native you reach for it when state is shared across many screens or is hard to thread through props — auth/session, cart, cached server data. The thing to know in 2026: you don't write plain Redux anymore. The standard is Redux Toolkit (RTK)configureStore and createSlice cut the boilerplate, ship Immer for "mutating" immutable updates, and include redux-thunk by default; RTK Query handles data fetching/caching. Plain createStore is deprecated.

Also worth saying: Redux is often overkill. For most apps, React Context, Zustand, or a server-cache library like RTK Query / React Query covers the need with far less ceremony — interviewers like candidates who know when not to use Redux.

↑ Back to top

Follow-up 1

Can you explain the concept of a single source of truth in Redux?

In Redux, the concept of a single source of truth means that the entire state of an application is stored in a single JavaScript object called the 'store'. This makes it easier to track and manage the state of an application, as all the state changes are handled through actions and reducers. Any component in the application can access the state from the store, ensuring that there is only one source of truth for the state.

Follow-up 2

How does Redux help in managing the state of a React Native application?

Redux helps in managing the state of a React Native application by providing a centralized store where all the state is stored. Components can dispatch actions to update the state, and reducers handle these actions to update the state in an immutable way. This makes it easier to track and manage the state changes in the application, as all the state changes are handled through a predictable flow.

Follow-up 3

What are the core principles of Redux?

The core principles of Redux are:

  1. Single source of truth: The entire state of an application is stored in a single JavaScript object called the 'store'.
  2. State is read-only: The state can only be modified by dispatching actions, which are plain JavaScript objects.
  3. Changes are made with pure functions: Reducers are pure functions that take the current state and an action, and return a new state.
  4. Changes are made with immutable data: The state is immutable, meaning it cannot be directly modified. Instead, new copies of the state are created with each change.
  5. Predictable state changes: Redux follows a unidirectional data flow pattern, making it easier to understand and debug the state changes in an application.

2. Can you explain the components of Redux?

The core Redux concepts are:

  1. Store: the single source of truth holding the whole app state. You read with getState/selectors, update via dispatch, and subscribe to changes.

  2. Actions: plain objects describing what happened, with a type and optional payload. They're dispatched to the store.

  3. Reducers: pure functions (state, action) => newState that return the next state immutably — no mutations, no side effects.

A 2026 interviewer expects you to frame these through Redux Toolkit, the current standard. createSlice generates the reducer and its action creators together, and uses Immer so you can write "mutating" code that's actually immutable:

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

const counter = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1; }, // Immer-safe
    addBy: (state, action) => { state.value += action.payload; },
  },
});

export const { increment, addBy } = counter.actions;
export const store = configureStore({ reducer: { counter: counter.reducer } });

Worth mentioning: middleware (thunk is included by default) sits between dispatch and the reducer for async/side effects, and selectors read derived state. RTK Query adds a data-fetching/caching layer on top.

↑ Back to top

Follow-up 1

What is a Redux Store?

The Redux store is a JavaScript object that holds the entire state of the application. It is the single source of truth for the state and is responsible for dispatching actions and updating the state based on the actions. The store is created using the createStore function from the Redux library.

Follow-up 2

Can you explain what Actions and Reducers are in Redux?

Actions and reducers are two important concepts in Redux:

  • Actions: Actions are plain JavaScript objects that represent an intention to change the state. They are dispatched to the store using the dispatch method and contain a type property that describes the type of action being performed. Actions can also contain additional data that is used by reducers to update the state.

  • Reducers: Reducers are pure functions that specify how the state should be updated based on the actions. They take the current state and an action as input and return a new state. Reducers are responsible for updating the state immutably, meaning they should not modify the existing state, but instead create a new state object with the updated values.

Follow-up 3

How do Middleware work in Redux?

Middleware in Redux provides a way to extend the behavior of the dispatch function. It sits between the dispatching of an action and the moment it reaches the reducer.

When an action is dispatched, it first goes through the middleware chain before reaching the reducer. Each middleware can intercept the action, modify it, dispatch additional actions, or perform asynchronous operations.

Middleware is specified when creating the Redux store using the applyMiddleware function from the Redux library. Some commonly used middleware include redux-thunk for handling asynchronous actions, redux-logger for logging actions and state changes, and redux-saga for more complex asynchronous flows.

3. How do you handle asynchronous actions in Redux?

Reducers must be pure, so async work goes in middleware. The standard in 2026 is Redux Toolkit's createAsyncThunk (thunk middleware is included with RTK by default), which dispatches pending / fulfilled / rejected actions you handle in a slice's extraReducers:

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

export const fetchUser = createAsyncThunk('user/fetch', async (id) => {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
});

const userSlice = createSlice({
  name: 'user',
  initialState: { data: null, status: 'idle' },
  reducers: {},
  extraReducers: (b) => {
    b.addCase(fetchUser.pending,   (s) => { s.status = 'loading'; })
     .addCase(fetchUser.fulfilled, (s, a) => { s.status = 'done'; s.data = a.payload; })
     .addCase(fetchUser.rejected,  (s) => { s.status = 'error'; });
  },
});

For data fetching/caching specifically, RTK Query is usually the better answer — it generates hooks with built-in loading/error/cache states. For complex, orchestrated side effects (cancellation, debouncing, long-running flows), redux-saga is still used. Plain createStore + manual thunk wiring is legacy.

↑ Back to top

Follow-up 1

What is Redux Thunk?

Redux Thunk is a middleware library for Redux that allows you to write action creators that return a function instead of an action object. This function can then be used to perform asynchronous operations and dispatch actions based on the result.

Follow-up 2

How does Redux Saga handle asynchronous actions?

Redux Saga is a middleware library for Redux that uses ES6 generators to make asynchronous code easier to read, write, and test. It allows you to define complex asynchronous flows as a series of simple, sequential steps.

Follow-up 3

Can you compare Redux Thunk and Redux Saga?

Both Redux Thunk and Redux Saga are middleware libraries for handling asynchronous actions in Redux. However, they have some differences:

  • Redux Thunk allows you to write action creators that return a function, while Redux Saga uses ES6 generators.
  • Redux Thunk is simpler and easier to get started with, while Redux Saga provides more advanced features and allows for more complex asynchronous flows.
  • Redux Thunk is more suitable for simple asynchronous operations, while Redux Saga is better suited for complex scenarios with multiple asynchronous actions and side effects.

Overall, the choice between Redux Thunk and Redux Saga depends on the complexity of your application and your personal preference for coding style.

4. How do you handle errors in Redux?

Error handling in Redux happens at the layer where the error occurs, and with Redux Toolkit the patterns are cleaner than the old manual approach:

  1. In async thunks (the common case):

    • With createAsyncThunk, a thrown error automatically dispatches the rejected action; you store the message in the slice via extraReducers and read it in the UI. Use rejectWithValue to pass a structured error payload: js export const login = createAsyncThunk('auth/login', async (creds, { rejectWithValue }) => { try { return await api.login(creds); } catch (e) { return rejectWithValue(e.response?.data ?? 'Login failed'); } }); // in extraReducers: builder.addCase(login.rejected, (s, a) => { s.error = a.payload; });
  2. In the slice/state: keep an explicit error (and status) field per feature so components can render error UI and retry.

  3. Centrally, via middleware: add custom middleware (or use redux-saga) to catch and log errors across actions — useful for reporting to Sentry/Crashlytics.

With RTK Query, errors come back automatically in the hook's error/isError fields, so you rarely hand-roll this. Gotcha: don't put side effects (like showing an alert) inside reducers — keep reducers pure and trigger UI reactions from the component or middleware.

↑ Back to top

Follow-up 1

How can you handle errors during the execution of an action?

To handle errors during the execution of an action in Redux, you can use a try-catch block inside your action creator. Here's an example:

export const fetchData = () => {
  return async (dispatch) => {
    try {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      dispatch({ type: 'FETCH_SUCCESS', payload: data });
    } catch (error) {
      dispatch({ type: 'FETCH_ERROR', payload: error.message });
    }
  };
};

In this example, the fetchData action creator makes an asynchronous request to fetch data from an API. If an error occurs during the execution of the fetch or response.json() functions, the catch block will be executed and a FETCH_ERROR action will be dispatched with the error message as the payload. This allows you to update the state to reflect the occurrence of an error.

Follow-up 2

How can you handle errors in reducers?

To handle errors in reducers in Redux, you can update the state to reflect the occurrence of an error. For example, you can add an error property to the state object and set it to the error message. Here's an example:

const initialState = {
  data: null,
  error: null
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'FETCH_SUCCESS':
      return { ...state, data: action.payload, error: null };
    case 'FETCH_ERROR':
      return { ...state, error: action.payload };
    default:
      return state;
  }
};

In this example, the reducer handles two action types: FETCH_SUCCESS and FETCH_ERROR. When a FETCH_SUCCESS action is dispatched, the state is updated with the fetched data and the error property is set to null. When a FETCH_ERROR action is dispatched, the state is updated with the error message and the data property is set to null. This allows you to handle errors in reducers and update the state accordingly.

Follow-up 3

What is the role of middleware in error handling in Redux?

Middleware in Redux plays a crucial role in error handling. It allows you to intercept actions and handle errors in a centralized way. Here's how middleware can be used for error handling in Redux:

  1. Handling errors during asynchronous operations:

    • Middleware like Redux Thunk or Redux Saga can be used to handle errors that occur during asynchronous operations. These middleware allow you to dispatch multiple actions, including error actions, based on the outcome of the asynchronous operation.
  2. Implementing custom error handling logic:

    • Middleware can be used to implement custom error handling logic. For example, you can create a middleware that logs errors to a remote server or displays error messages to the user.

By using middleware for error handling, you can keep your action creators and reducers focused on their primary responsibilities and delegate error handling to a centralized middleware.

5. How do you test Redux reducers and actions?

Reducers are pure functions, so they're the easiest thing to test — call the reducer with a starting state and an action and assert the result. No store, no mocks. With Redux Toolkit you test the slice's reducer and its generated action creators together:

import reducer, { addTodo } from './todosSlice';

it('handles addTodo', () => {
  const start = { items: [] };
  const next = reducer(start, addTodo('Buy milk'));
  expect(next.items).toEqual(['Buy milk']);
  expect(start.items).toEqual([]); // original untouched (immutability)
});

Async thunks can't be tested as pure functions — dispatch them against a real store (cheap to build with configureStore) and assert the resulting state, mocking only the network layer:

it('fetchUser populates state', async () => {
  fetchMock.mockResponse(JSON.stringify({ id: 1, name: 'Ada' }));
  const store = configureStore({ reducer: { user: userReducer } });
  await store.dispatch(fetchUser(1));
  expect(store.getState().user.data.name).toBe('Ada');
});

Tooling note: Jest is the RN standard; pair it with React Native Testing Library for component-level tests that dispatch through a ``. Prefer testing observable state over asserting exact dispatched-action sequences, which is brittle.

↑ Back to top

Follow-up 1

What are the best practices for testing Redux reducers?

Here are some best practices for testing Redux reducers:

  1. Test each reducer function separately: Write separate test cases for each reducer function to ensure that they produce the expected state for different actions.

  2. Use meaningful test case names: Name your test cases in a way that describes the action being tested and the expected outcome.

  3. Test the initial state: Verify that the reducer returns the initial state when no action is provided.

  4. Test the default case: If your reducer has a default case that returns the current state for unknown actions, make sure to test it.

  5. Use immutable data structures: Redux encourages the use of immutable data structures. Make sure to test that your reducer functions return new state objects instead of modifying the existing state.

  6. Use snapshot testing: Snapshot testing can be a useful technique to quickly verify that the reducer produces the expected state for a given action.

Remember to also test edge cases and handle error scenarios in your reducer tests.

Follow-up 2

How can you test asynchronous actions in Redux?

To test asynchronous actions in Redux, you can use a testing library like Jest along with a mocking library like redux-mock-store or redux-saga-test-plan.

Here's an example of how you can test an asynchronous action using redux-mock-store:

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { fetchTodos, FETCH_TODOS_SUCCESS } from './actions';

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

// Test case for an asynchronous action
it('should dispatch FETCH_TODOS_SUCCESS action', () => {
  const expectedActions = [{ type: FETCH_TODOS_SUCCESS, payload: ['Buy milk', 'Walk the dog'] }];
  const store = mockStore({ todos: [] });
  return store.dispatch(fetchTodos()).then(() => {
    expect(store.getActions()).toEqual(expectedActions);
  });
});

In this example, redux-mock-store is used to create a mock store with the initial state. The fetchTodos action is then dispatched, and the expected actions are asserted using store.getActions().

You can also use redux-saga-test-plan if you are using Redux Saga for handling asynchronous actions in your Redux application.

Follow-up 3

Can you explain the concept of mocking in testing Redux actions?

Mocking in testing Redux actions involves creating fake or mock dependencies to simulate certain behavior or data. This is useful when you want to isolate the action being tested from its dependencies, such as the Redux store's dispatch function or external APIs.

By mocking these dependencies, you can control their behavior and verify that the action behaves as expected in different scenarios.

For example, you can use Jest's jest.fn() to create a mock function that simulates the Redux store's dispatch function. You can then assert that the mock function is called with the expected action.

Here's an example of how you can mock the Redux store's dispatch function using Jest:

import { addTodo } from './actions';

// Test case for a specific action
it('should dispatch ADD_TODO action', () => {
  const dispatch = jest.fn();
  const todo = 'Buy milk';
  addTodo(todo)(dispatch);
  expect(dispatch).toHaveBeenCalledWith({ type: 'ADD_TODO', payload: todo });
});

In this example, jest.fn() is used to create a mock function for the dispatch function. The addTodo action is then called with the mock dispatch function, and the expected action is asserted using toHaveBeenCalledWith().

Mocking can also be used to simulate API calls or other asynchronous operations in Redux actions. You can create mock functions for the APIs and assert that they are called with the expected parameters.

Live mock interview

Mock interview: Redux in React Native

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.