Popular Searches
Popular Course Categories
Popular Courses

Understanding Redux and Redux Toolkit

Understanding Redux and Redux Toolkit

State Management in React Native

Understanding Redux and Redux Toolkit in React Native

Course: React Native Training

Topic: Understanding Redux and Redux Toolkit

Course Link: React Native Training Course

Demo Link: Register for a Course Demo

JustAcademy: Visit JustAcademy


1. Introduction to Redux and Redux Toolkit

React Native applications often contain multiple screens and reusable components that need access to the same information. For example, an e-commerce application may need to share shopping cart items between product listings, a cart screen, and checkout. A social application may share user information, notifications, and saved posts across several screens.

Managing shared information with local component state and props can become difficult as an application grows. Redux provides a predictable approach to managing shared application state in a centralized store.

Redux Toolkit (RTK) is the officially recommended way to write Redux logic. It simplifies store configuration, action creation, reducer logic, immutable updates, and asynchronous workflows. It reduces much of the repetitive code associated with older Redux patterns. Read the official Redux introduction.

Learning Objectives

  • Understand Redux, Redux Toolkit, and their relationship with React Native.
  • Learn the store, actions, reducers, dispatch, and selectors.
  • Understand the Redux one-way data flow.
  • Install and configure Redux Toolkit and React Redux.
  • Create slices with createSlice().
  • Configure the store using configureStore().
  • Connect React Native components using Provider, useSelector(), and useDispatch().
  • Manage shared state with a practical counter and shopping cart example.
  • Handle asynchronous operations with createAsyncThunk().
  • Understand RTK Query, debugging, testing, and best practices.

2. What Is Redux?

Redux is a JavaScript library for predictable global state management. It allows an application to keep shared state in a store and update that state through explicit actions processed by reducer functions.

Instead of each screen maintaining a separate copy of shared information, components can read from the same store. When an action changes the store's state, components that select the affected data can update their displayed UI.

Example Use Cases

  • E-commerce: Shopping cart items, quantities, and selected products.
  • Authentication UI: Current user information and login status, while secure credentials require appropriate secure handling.
  • Social applications: Saved posts, reactions, and shared interface preferences.
  • Task management: Tasks, filters, and completion status.
  • Learning applications: Selected courses, learning progress, and user preferences.
  • Booking applications: Selected options and multi-step booking details that must be shared across screens.

When Should Redux Be Considered?

Redux is useful when many components need the same data, state updates involve multiple related actions, or the application benefits from centralized state logic and predictable updates. It is not necessary for every application. Small values such as a modal's visibility or one input field can usually remain in local component state.

3. What Is Redux Toolkit?

Redux Toolkit is the official toolset for writing Redux applications with less boilerplate and sensible defaults. It includes APIs for creating stores, defining reducers and actions, handling asynchronous logic, and working with server data.

Why Was Redux Toolkit Introduced?

Traditional Redux patterns could require developers to write separate action type constants, action creators, reducer switch statements, store configuration, and immutable update logic. Redux Toolkit simplifies these common tasks and helps prevent mistakes.

Traditional Redux Pattern Redux Toolkit Approach
Manually create many action type constants createSlice() generates action types and action creators
Write reducer switch statements for each action Define named reducer functions inside a slice
Manually configure the store and middleware configureStore() provides useful defaults
Manually write immutable updates Immer allows mutation-like syntax inside supported reducer functions
Write boilerplate for asynchronous action lifecycles createAsyncThunk() generates pending, fulfilled, and rejected action lifecycles
Build custom data-fetching and caching logic for many common cases RTK Query offers an optional API data-fetching and caching solution

Redux Toolkit does not replace Redux's core principles. It provides a more convenient and safer way to implement them. Explore the Redux Toolkit documentation.

4. Core Concepts of Redux

4.1 Store

The store is the central object that holds the application's Redux state. An application commonly uses one store, with different state sections managed by separate slice reducers.

For example, a store might contain:

{
  counter: {
    value: 5
  },
  cart: {
    items: []
  },
  preferences: {
    theme: "light"
  }
}

This is an illustrative state structure, not a requirement that every application use these exact properties.

4.2 State

State is the current data stored in the Redux store. It is normally represented as a JavaScript object tree. State should be updated through dispatched actions and reducer logic rather than by directly changing the store from a component.

4.3 Action

An action describes an event or something that happened in the application. A Redux action is a plain JavaScript object with a type field and may include a payload containing additional data.

{
  type: "counter/increment",
  payload: 1
}

With Redux Toolkit, action creators are generally generated automatically by createSlice().

4.4 Reducer

A reducer is a function that calculates the next state based on the current state and an action. Redux Toolkit slice reducers use Immer internally, so developers can write mutation-like update statements while the library produces immutable state updates.

4.5 Dispatch

Dispatch sends an action to the Redux store. The relevant reducer logic processes the action, and the store calculates the next state.

4.6 Selector

A selector is a function that reads or derives a value from the store's state. In React components, useSelector() subscribes the component to the selected result.

5. Understanding the Redux Data Flow

Redux follows a predictable, one-way data flow. A user interacts with a component, the component dispatches an action, the reducer calculates the updated state, and the component reads the resulting state through a selector.

  1. User interaction: A user presses a button or performs another action.
  2. Dispatch: The component dispatches an action.
  3. Reducer: The relevant reducer handles the action and calculates the next state.
  4. Store update: The Redux store holds the updated state.
  5. Selector: Components select the data they need.
  6. UI update: Components using changed selected data can render the updated interface.

Example Flow

User presses "Increase"
        |
        v
dispatch(increment())
        |
        v
Counter slice reducer
        |
        v
Redux store updates counter.value
        |
        v
useSelector reads new value
        |
        v
Counter component displays new value

This separation makes application behavior easier to understand, test, and debug.

6. Installing Redux Toolkit in a React Native Project

To use Redux Toolkit with React Native, install Redux Toolkit and React Redux in the existing project. React Redux connects Redux with React components and provides the Provider component and Hooks.

Installation Command

npm install @reduxjs/toolkit react-redux

For a Yarn project, use:

yarn add @reduxjs/toolkit react-redux

These packages are suitable for an existing React Native project. If you are starting a new Expo project, you can also consider the official Redux Toolkit React Native template.

Official Redux Toolkit Getting Started Guide

7. Recommended Project Structure

A clear file structure separates store configuration, feature-specific state logic, and UI components. The exact structure can be adjusted to the size and conventions of the project.

src/
  app/
    store.js
  features/
    counter/
      counterSlice.js
      CounterScreen.js
    cart/
      cartSlice.js
      CartScreen.js
  App.js

Purpose of Each File

  • store.js: Configures the Redux store and combines feature reducers.
  • counterSlice.js: Defines counter state, reducer logic, and generated actions.
  • CounterScreen.js: Displays counter data and dispatches actions.
  • cartSlice.js: Manages shared shopping cart data.
  • CartScreen.js: Displays cart items and available cart actions.
  • App.js: Wraps the application with the Redux provider.

8. Creating a Redux Slice with createSlice()

The createSlice() function is one of the most important Redux Toolkit APIs. It accepts a slice name, an initial state, and reducer functions. It generates a reducer and corresponding action creators.

Basic Syntax

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

const exampleSlice = createSlice({
  name: 'example',
  initialState: {},
  reducers: {
    // Define reducer functions here
  }
});

export const {} = exampleSlice.actions;
export default exampleSlice.reducer;

Example: Counter Slice

Create a file named src/features/counter/counterSlice.js.

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

const initialState = {
  value: 0
};

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

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

export default counterSlice.reducer;

Explanation

  • name identifies the slice and helps generate action type names.
  • initialState defines the initial value for this state section.
  • reducers contains functions that handle particular actions.
  • increment increases the counter by one.
  • decrement decreases the counter by one.
  • incrementByAmount adds the value supplied through action.payload.
  • reset resets the counter to zero.
  • counterSlice.actions exports the generated action creators.
  • counterSlice.reducer exports the reducer used by the store.

Although the reducer functions appear to mutate state, Redux Toolkit uses Immer to produce immutable updates. This mutation-like syntax is appropriate inside createSlice() reducer functions, but should not be copied into ordinary component code that directly mutates Redux state.

9. Configuring the Redux Store with configureStore()

The configureStore() API creates the Redux store with sensible defaults. It can combine slice reducers, include thunk middleware, enable development checks, and integrate with Redux DevTools when available.

Create the Store

Create src/app/store.js.

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

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

Understanding the Configuration

  • configureStore() creates the store.
  • The reducer option connects reducers to the store.
  • The key counter determines where this slice appears in the root state.
  • The counter data can now be accessed through state.counter.

For example, the counter value can be selected with state.counter.value. The state path depends on the keys used in the store configuration.

10. Connecting Redux to the React Native App with Provider

The React Redux Provider makes the Redux store available to components beneath it. Without the provider, components using React Redux Hooks cannot access the configured store through the normal context mechanism.

Example: App.js

import React from 'react';
import { Provider } from 'react-redux';
import { store } from './src/app/store';
import CounterScreen from './src/features/counter/CounterScreen';

export default function App() {
  return (
    <Provider store={store}>
      <CounterScreen />
    </Provider>
  );
}

Adjust the import paths to match your project's actual folder structure. In an existing application, wrap the appropriate root component or navigation tree with the provider so all required screens can access the store.

11. Reading State with useSelector()

The useSelector() Hook from React Redux lets a component read data from the Redux store. It subscribes the component to the selected result so it can update when that result changes.

Syntax

const selectedValue = useSelector(state => state.someSlice.someValue);

Example: Read Counter Value

import React from 'react';
import { View, Text } from 'react-native';
import { useSelector } from 'react-redux';

export default function CounterDisplay() {
  const count = useSelector(state => state.counter.value);

  return (
    <View>
      <Text>Current Count: {count}</Text>
    </View>
  );
}

The selector reads value from the counter slice. When that selected value changes, React Redux can trigger a re-render of the component.

12. Updating State with useDispatch()

The useDispatch() Hook returns the store's dispatch function. A component uses it to send generated actions to the Redux store.

Example: Counter Screen

Create src/features/counter/CounterScreen.js.

import React from 'react';
import { View, Text, Button } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import {
  increment,
  decrement,
  incrementByAmount,
  reset
} from './counterSlice';

export default function CounterScreen() {
  const count = useSelector(state => state.counter.value);
  const dispatch = useDispatch();

  return (
    <View>
      <Text>Count: {count}</Text>
      <Button
        title="Increase"
        onPress={() => dispatch(increment())}
      />
      <Button
        title="Decrease"
        onPress={() => dispatch(decrement())}
      />
      <Button
        title="Add 5"
        onPress={() => dispatch(incrementByAmount(5))}
      />
      <Button
        title="Reset"
        onPress={() => dispatch(reset())}
      />
    </View>
  );
}

How the Example Works

  1. The component reads the current count with useSelector().
  2. The component obtains the dispatch function with useDispatch().
  3. Pressing a button dispatches the relevant action.
  4. The counter reducer calculates the updated value.
  5. The store holds the updated counter state.
  6. The component reads the new selected value and updates the display.

13. Understanding Actions and Payloads

An action creator generated by createSlice() creates an action object. If an action creator receives an argument, that value normally becomes the action's payload.

Example

dispatch(incrementByAmount(5));

The action generated by this call is conceptually similar to:

{
  type: 'counter/incrementByAmount',
  payload: 5
}

The reducer reads the value through action.payload and uses it to update the counter.

Example with a String Payload

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

const messageSlice = createSlice({
  name: 'message',
  initialState: {
    text: ''
  },
  reducers: {
    setMessage(state, action) {
      state.text = action.payload;
    },
    clearMessage(state) {
      state.text = '';
    }
  }
});

export const { setMessage, clearMessage } = messageSlice.actions;
export default messageSlice.reducer;

A component can dispatch setMessage('Hello from Redux') to update the message in the store.

14. Adding Multiple Slices to the Store

A growing application commonly separates state by feature. For example, counter state, cart state, and user preferences can be maintained in separate slices. The store combines these reducers into a single root state.

Example Store Configuration

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
import cartReducer from '../features/cart/cartSlice';

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

The root state now has separate counter and cart properties. A component can select data from either slice using the corresponding state path.

Benefits of Feature-Based Slices

  • Related state and reducer logic stay together.
  • Different developers can work on separate features more easily.
  • Actions are generated close to the logic that handles them.
  • The store configuration remains readable as the application grows.
  • Each slice can be tested independently.

15. Understanding Immutable State Updates and Immer

Redux state should be updated immutably. This means existing state objects and arrays should not be modified directly outside the supported reducer mechanisms.

Redux Toolkit uses Immer in createSlice() and createReducer(). Immer lets reducer functions use mutation-like syntax while generating the appropriate immutable updates.

Example: Updating an Object

const profileSlice = createSlice({
  name: 'profile',
  initialState: {
    name: 'Riya',
    role: 'Student'
  },
  reducers: {
    updateName(state, action) {
      state.name = action.payload;
    }
  }
});

Here, state.name = action.payload is valid because it appears inside a Redux Toolkit slice reducer. Immer creates the next state while preserving Redux's immutability requirements.

Example: Updating an Array

const tasksSlice = createSlice({
  name: 'tasks',
  initialState: {
    items: []
  },
  reducers: {
    addTask(state, action) {
      state.items.push(action.payload);
    },
    removeTask(state, action) {
      state.items = state.items.filter(
        task => task.id !== action.payload
      );
    }
  }
});

The reducer syntax is concise, and Immer handles the immutable update process. Do not assume that direct mutation is safe in ordinary React components or outside the supported Redux Toolkit reducer context.

16. Handling Asynchronous Operations with createAsyncThunk()

Applications often need to load data from a remote API, submit a form, or retrieve a user's information. These operations are asynchronous because the application must wait for an external result.

createAsyncThunk() generates a thunk action creator and lifecycle actions for an asynchronous operation. The lifecycle actions are:

  • pending: The operation has started.
  • fulfilled: The operation completed successfully.
  • rejected: The operation failed or was rejected.

Example: Fetch User Data

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

export const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId, thunkAPI) => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/users/${userId}`
    );

    if (!response.ok) {
      throw new Error('Failed to fetch user');
    }

    return await response.json();
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    status: 'idle',
    error: null
  },
  reducers: {},
  extraReducers: builder => {
    builder
      .addCase(fetchUser.pending, state => {
        state.status = 'loading';
        state.error = null;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.error.message || 'Something went wrong';
      });
  }
});

export default userSlice.reducer;

Explanation

  • createAsyncThunk() defines the asynchronous operation.
  • The API request fetches a user by ID.
  • The pending reducer sets the status to loading.
  • The fulfilled reducer stores the returned user data.
  • The rejected reducer records an error state.
  • The component can use the status and data to render loading, success, and error interfaces.

This example is intended to teach the asynchronous Redux pattern. For a real application, also consider request cancellation, authentication, response validation, retry behavior, and whether a server-state library would be more suitable.

Dispatching the Async Thunk

import React from 'react';
import { View, Text, Button } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUser } from './userSlice';

export default function UserScreen() {
  const dispatch = useDispatch();
  const { data, status, error } = useSelector(state => state.user);

  return (
    <View>
      <Button
        title="Load User"
        onPress={() => dispatch(fetchUser(1))}
      />

      {status === 'loading' && <Text>Loading...</Text>}
      {status === 'failed' && <Text>{error}</Text>}
      {status === 'succeeded' && data && (
        <Text>User: {data.name}</Text>
      )}
    </View>
  );
}

To use this screen, register userReducer in the store under the user key. The selector then reads the state from state.user.

17. Understanding RTK Query

RTK Query is an optional data-fetching and caching solution included with Redux Toolkit. It can reduce the amount of custom code needed to make API requests, cache results, track loading and error states, and refresh data when appropriate.

createAsyncThunk() is useful for custom asynchronous workflows. RTK Query is often a better fit when the main task is fetching, caching, and synchronizing server data.

Example: Defining an API Service

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const postsApi = createApi({
  reducerPath: 'postsApi',
  baseQuery: fetchBaseQuery({
    baseUrl: 'https://jsonplaceholder.typicode.com/'
  }),
  endpoints: builder => ({
    getPosts: builder.query({
      query: () => 'posts'
    })
  })
});

export const { useGetPostsQuery } = postsApi;

Registering RTK Query in the Store

import { configureStore } from '@reduxjs/toolkit';
import { postsApi } from '../features/posts/postsApi';

export const store = configureStore({
  reducer: {
    [postsApi.reducerPath]: postsApi.reducer
  },
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware().concat(postsApi.middleware)
});

Using the Generated Query Hook

import React from 'react';
import { View, Text } from 'react-native';
import { useGetPostsQuery } from './postsApi';

export default function PostsScreen() {
  const { data, error, isLoading } = useGetPostsQuery();

  if (isLoading) {
    return <Text>Loading posts...</Text>;
  }

  if (error) {
    return <Text>Unable to load posts.</Text>;
  }

  return (
    <View>
      {data?.slice(0, 10).map(post => (
        <Text key={post.id}>{post.title}</Text>
      ))}
    </View>
  );
}

Make sure the API reducer and middleware are registered in the store before using the generated query Hook. In larger applications, RTK Query can also support mutations, cache invalidation, and more advanced API workflows.

18. Redux Toolkit vs React Context

React Context and Redux Toolkit can both help share state, but they serve somewhat different needs.

Feature React Context Redux Toolkit
Main purpose Make values available to descendant components Provide structured, predictable global state management
Setup Context and provider, often with state Hooks Store, slices, provider, and React Redux Hooks
Update logic Defined by the application Organized through actions and reducers
Async workflows Must be implemented with suitable Hooks or other tools Supports thunks and RTK Query as well as custom middleware
Debugging Depends on the application's setup Supports Redux DevTools integration when available
Best suited for Simple shared values such as themes or locale Complex shared state with multiple actions and coordinated updates

Neither approach is always better. Use Context for straightforward shared values when it is sufficient. Consider Redux Toolkit when centralized state logic, structured updates, middleware, or extensive debugging support are useful.

19. Redux Toolkit vs Traditional Redux

Aspect Traditional Redux Style Redux Toolkit
Action creators Often written manually Generated by createSlice()
Action type constants Often declared manually Generated from slice names and reducer names
Reducer logic Often uses switch statements Uses named case reducers
Store configuration Requires more manual setup Simplified through configureStore()
Immutable updates Copies must be managed manually or with other utilities Immer supports mutation-like syntax in slice reducers
Recommended for new Redux code Generally not preferred as the default approach Yes, it is the official recommended approach

20. Best Practices for Redux Toolkit in React Native

  1. Use Redux Toolkit for new Redux logic: Prefer createSlice() and configureStore() instead of writing repetitive setup manually.
  2. Organize by feature: Keep a feature's state, actions, and reducers close together.
  3. Keep local state local: Do not move every text input, modal, or temporary UI flag into Redux without a reason.
  4. Use selectors: Read only the state required by a component.
  5. Keep reducers predictable: Reducers should calculate state updates without performing network requests or other side effects.
  6. Handle async status explicitly: Represent loading, success, and failure states where necessary.
  7. Use RTK Query for suitable API workflows: Avoid rebuilding caching and request lifecycle management unnecessarily.
  8. Keep store data serializable: Prefer plain objects, arrays, strings, numbers, booleans, and null. Avoid storing functions, promises, class instances, and other non-serializable values unless there is a specific, well-understood reason.
  9. Protect sensitive data: Redux is an in-memory state manager, not secure storage. Handle tokens and other sensitive values using appropriate security practices.
  10. Use clear names: Choose meaningful slice names, action names, and state properties.
  11. Test reducers and components: Verify expected state transitions and UI behavior.
  12. Debug systematically: Use logs or Redux DevTools where supported and inspect dispatched actions and state changes.

21. Common Redux Toolkit Mistakes

Mistake Possible Cause Solution
Selector returns undefined Incorrect state path or missing reducer registration Check the store keys and selector path.
Hooks cannot find the store Component is outside the Redux provider Wrap the relevant component tree with Provider.
Action does not update state Wrong action imported or reducer not registered Check the action creator and store configuration.
Payload is missing Action was dispatched without the expected argument Check the dispatch call and reducer's payload usage.
API request never displays results Async reducer cases or loading UI are incorrect Inspect the request status, payload, and error handling.
Non-serializable value warnings Functions, class instances, or other unsupported values are stored in Redux Keep state serializable whenever possible.
Too much Redux code All UI state has been moved into the store Keep simple component state local and promote only genuinely shared data.
Duplicate API data The same server response is maintained in several places Consider RTK Query or another deliberate server-state strategy.

22. Practical Project: Shopping Cart with Redux Toolkit

Project Objective: Build a simple shopping cart where products can be added and removed, quantities can be tracked, and the cart total is shared across the application.

Project Requirements

  • Create a Redux store with a cart slice.
  • Define actions for adding items, removing items, and clearing the cart.
  • Display the number of items in the cart.
  • Calculate and display the cart total.
  • Use useSelector() to read cart information.
  • Use useDispatch() to dispatch cart actions.
  • Wrap the application with the Redux provider.

Step 1: Create cartSlice.js

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

const initialState = {
  items: []
};

const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    addItem(state, action) {
      const product = action.payload;
      const existingItem = state.items.find(
        item => item.id === product.id
      );

      if (existingItem) {
        existingItem.quantity += 1;
      } else {
        state.items.push({
          ...product,
          quantity: 1
        });
      }
    },
    removeItem(state, action) {
      state.items = state.items.filter(
        item => item.id !== action.payload
      );
    },
    decreaseQuantity(state, action) {
      const item = state.items.find(
        item => item.id === action.payload
      );

      if (item) {
        if (item.quantity > 1) {
          item.quantity -= 1;
        } else {
          state.items = state.items.filter(
            current => current.id !== action.payload
          );
        }
      }
    },
    clearCart(state) {
      state.items = [];
    }
  }
});

export const {
  addItem,
  removeItem,
  decreaseQuantity,
  clearCart
} = cartSlice.actions;

export default cartSlice.reducer;

Step 2: Configure store.js

import { configureStore } from '@reduxjs/toolkit';
import cartReducer from '../features/cart/cartSlice';

export const store = configureStore({
  reducer: {
    cart: cartReducer
  }
});

Step 3: Add the Provider

import React from 'react';
import { Provider } from 'react-redux';
import { store } from './src/app/store';
import CartScreen from './src/features/cart/CartScreen';

export default function App() {
  return (
    <Provider store={store}>
      <CartScreen />
    </Provider>
  );
}

Step 4: Create CartScreen.js

import React from 'react';
import { View, Text, Button } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import {
  addItem,
  removeItem,
  decreaseQuantity,
  clearCart
} from './cartSlice';

const sampleProduct = {
  id: 'p1',
  name: 'Wireless Headphones',
  price: 1999
};

export default function CartScreen() {
  const items = useSelector(state => state.cart.items);
  const dispatch = useDispatch();

  const itemCount = items.reduce(
    (total, item) => total + item.quantity,
    0
  );

  const cartTotal = items.reduce(
    (total, item) => total + item.price * item.quantity,
    0
  );

  return (
    <View>
      <Text>Shopping Cart</Text>
      <Text>Items: {itemCount}</Text>

      <Button
        title="Add Headphones"
        onPress={() => dispatch(addItem(sampleProduct))}
      />

      {items.map(item => (
        <View key={item.id}>
          <Text>
            {item.name} - ₹{item.price} × {item.quantity}
          </Text>
          <Button
            title="Decrease Quantity"
            onPress={() => dispatch(decreaseQuantity(item.id))}
          />
          <Button
            title="Remove"
            onPress={() => dispatch(removeItem(item.id))}
          />
        </View>
      ))}

      <Text>Total: ₹{cartTotal.toFixed(2)}</Text>

      <Button
        title="Clear Cart"
        onPress={() => dispatch(clearCart())}
      />
    </View>
  );
}

Project Explanation

  • The cart slice defines all cart-related state transitions.
  • The store registers the cart reducer under the cart key.
  • The provider makes the store available to the screen.
  • The screen reads cart items with useSelector().
  • Buttons dispatch actions to add, decrease, remove, or clear items.
  • The item count and total are derived from the cart state.

This is a learning project. A production cart may need inventory checks, product validation, discounts, tax calculations, currency handling, and server-side price verification. Client-side totals should not be treated as authoritative for payment processing.

23. Testing and Debugging Redux Toolkit

Testing Reducers

Reducers can be tested by passing an initial state and an action, then checking the returned state. This makes it possible to test application logic independently of the user interface.

import counterReducer, {
  increment,
  reset
} from './counterSlice';

test('increments the counter', () => {
  const initialState = { value: 0 };
  const nextState = counterReducer(initialState, increment());

  expect(nextState.value).toBe(1);
});

test('resets the counter', () => {
  const currentState = { value: 8 };
  const nextState = counterReducer(currentState, reset());

  expect(nextState.value).toBe(0);
});

This example uses Jest-style syntax. Ensure your React Native project has a compatible test setup before running it.

Debugging Checklist

  • Verify the store configuration and reducer keys.
  • Confirm the application is wrapped with the correct provider.
  • Inspect the action being dispatched.
  • Check that the reducer handles the action as expected.
  • Verify the selector uses the correct state path.
  • Inspect asynchronous request status and error details.
  • Use Redux DevTools when supported by the development environment.
  • Test edge cases, including empty arrays, missing data, and failed requests.

24. Practice Exercises

  1. Create a counter slice with increment, decrement, reset, and increment-by-amount actions.
  2. Build a task slice with add, remove, and toggle-completion actions.
  3. Create a theme slice that switches between light and dark modes.
  4. Build a favorites feature where users can save and remove product IDs.
  5. Create an async thunk that fetches a list of products and handles loading, success, and failure states.
  6. Register two slices in the same Redux store and read data from both.
  7. Create a shopping cart with quantities, item removal, and a derived total.
  8. Refactor a small application that uses many props to share the same data and decide whether Redux Toolkit is appropriate.
  9. Write unit tests for a slice reducer and its generated actions.
  10. Explore RTK Query and compare it with a custom async thunk for fetching API data.

25. Interview Questions and Answers

Q1. What is Redux?

Answer: Redux is a JavaScript library for predictable state management. It stores shared application state centrally and updates it through actions and reducer functions.

Q2. What is Redux Toolkit?

Answer: Redux Toolkit is the officially recommended toolset for writing Redux logic. It simplifies store configuration, reducers, action creators, immutable updates, and common asynchronous workflows.

Q3. What is a Redux store?

Answer: A store holds the application's Redux state and provides the mechanism through which actions are dispatched and state updates are processed.

Q4. What is an action?

Answer: An action describes an event or change request. It includes a type and may contain a payload with additional information.

Q5. What is a reducer?

Answer: A reducer calculates the next state based on the current state and an action. Redux Toolkit uses Immer inside slice reducers to support concise immutable updates.

Q6. What is dispatch?

Answer: Dispatch sends an action to the Redux store so the relevant reducer logic can process it.

Q7. What does createSlice() do?

Answer: It creates a feature slice from an initial state, a name, and reducer functions. It also generates matching action creators and action types.

Q8. What does configureStore() do?

Answer: It configures the Redux store, combines slice reducers when supplied as an object, and provides useful default middleware and development tooling integration.

Q9. What is the purpose of Provider?

Answer: The React Redux provider makes the store available to descendant components through React context.

Q10. What is useSelector()?

Answer: It is a React Redux Hook that reads a selected value from the Redux store and subscribes the component to changes in that selected result.

Q11. What is useDispatch()?

Answer: It returns the dispatch function that components can use to dispatch Redux actions.

Q12. What is action.payload?

Answer: The payload contains data associated with an action, such as a quantity to add, a task to create, or a user ID to fetch.

Q13. Why is Redux Toolkit preferred over older Redux patterns?

Answer: It reduces boilerplate, simplifies store setup, generates action creators, supports concise immutable reducer updates, and provides common utilities for asynchronous logic.

Q14. What is createAsyncThunk()?

Answer: It creates a thunk for an asynchronous operation and generates pending, fulfilled, and rejected lifecycle actions.

Q15. What is RTK Query?

Answer: RTK Query is an optional Redux Toolkit feature for defining API endpoints and managing data fetching, caching, loading states, and related server-data workflows.

Q16. What is the difference between Redux Toolkit and Context?

Answer: Context makes values available to descendant components, while Redux Toolkit provides a structured store and action-based update system. The appropriate choice depends on the complexity and sharing requirements of the application.

Q17. Should all React Native state be stored in Redux?

Answer: No. Component-specific state should usually remain local. Redux is most useful for data that needs to be shared or managed centrally.

Q18. Is Redux state automatically persistent?

Answer: No. Redux state normally exists in memory. Persistence requires an explicit storage and restoration strategy.

Q19. Can reducer functions perform API calls?

Answer: Reducers should remain predictable and free of side effects. API requests should be handled through an appropriate asynchronous mechanism such as thunks or RTK Query.

Q20. How can Redux logic be tested?

Answer: Reducers can be tested by supplying an initial state and action and asserting the next state. Components and asynchronous workflows can also be tested with appropriate test utilities.

26. Quick Revision Checklist

  • Redux manages shared application state in a central store.
  • Redux Toolkit is the officially recommended way to write Redux logic.
  • Actions describe events, and reducers calculate state updates.
  • createSlice() generates reducers, action creators, and action types.
  • configureStore() configures the Redux store.
  • Provider makes the store available to the React component tree.
  • useSelector() reads selected state from the store.
  • useDispatch() dispatches actions.
  • Action payloads carry additional data for reducers.
  • Immer supports mutation-like syntax inside Redux Toolkit slice reducers.
  • createAsyncThunk() supports asynchronous workflows with lifecycle actions.
  • RTK Query provides API fetching and caching utilities.
  • Keep component-specific UI state local when possible.
  • Redux state does not automatically persist after an app restart.
  • Test reducers, asynchronous behavior, selectors, and component updates.

27. Conclusion

Redux and Redux Toolkit help React Native developers manage shared application state in a structured and predictable way. Redux provides the core concepts of a store, actions, reducers, and one-way data flow, while Redux Toolkit simplifies implementation with APIs such as createSlice(), configureStore(), and createAsyncThunk().

React Redux connects the store to components through Provider, useSelector(), and useDispatch(). For API-driven features, RTK Query can simplify data fetching and caching. The most effective approach is to use Redux when shared state benefits from centralized management, keep simple UI state local, and organize each feature with clear state transitions and tests.

Official Learning Resources:

whatsapp