Redux Tutorial: From Beginner to Advanced

Complete React Redux Tutorial (Beginner to Advanced)

Complete React Redux Tutorial (Beginner to Advanced)

Introduction

Redux is a predictable state container for JavaScript apps, most commonly used with React. It helps you manage your application's state in a centralized way, enabling better debugging, testing, and consistency.

This tutorial will take you from beginner to advanced level and cover:

  • Core Redux concepts (store, actions, reducers, dispatch)
  • Classic Redux vs Redux Toolkit
  • JavaScript and TypeScript examples
  • RTK Query for API calls
  • Real-world usage, best practices, and common pitfalls

Core Redux Concepts

Redux operates on a few fundamental concepts:

  • Store: The global state container.
  • Actions: Plain objects that describe what happened.
  • Reducers: Pure functions that update the state based on actions.
  • Dispatch: The method used to send actions to the store.
  • Selectors: Functions that retrieve specific parts of the state.

Imagine a restaurant:

  • The customer (React component) fills out an order form (action).
  • The waiter (dispatch function) delivers the order.
  • The kitchen (reducer) prepares the meal based on the order.
  • The counter (store) holds the completed order (state).

Store Setup

Classic Redux

// store.js
import { createStore } from 'redux';
import rootReducer from './reducers';

const store = createStore(rootReducer);
export default store;

Redux Toolkit

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

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

Actions and Reducers

Classic Redux

// actions.js
export const increment = () => ({ type: 'INCREMENT' });

// reducer.js
const initialState = { count: 0 };
export default function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    default:
      return state;
  }
}

Redux Toolkit

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

const counterSlice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment(state) {
      state.count += 1;
    },
  },
});

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

Store Configuration

Classic Redux requires manual setup of middleware like redux-thunk. Redux Toolkit includes thunk by default and sets up DevTools automatically.

Async Logic (Thunks & createAsyncThunk)

Classic Redux Thunk

// thunk.js
export const fetchUser = (id) => async (dispatch) => {
  dispatch({ type: 'FETCH_USER_START' });
  try {
    const res = await fetch(`/api/user/${id}`);
    const data = await res.json();
    dispatch({ type: 'FETCH_USER_SUCCESS', payload: data });
  } catch (err) {
    dispatch({ type: 'FETCH_USER_ERROR', error: err });
  }
};

RTK createAsyncThunk

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

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

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

Selectors & Memoization

// selectors.js
export const selectCount = (state) => state.counter.count;

// Reselect example
import { createSelector } from 'reselect';
export const selectEvenCount = createSelector(
  [selectCount],
  (count) => count % 2 === 0
);

Normalization with Entity Adapter

// postsSlice.js
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';

const postsAdapter = createEntityAdapter();
const initialState = postsAdapter.getInitialState();

const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    addPost: postsAdapter.addOne,
    removePost: postsAdapter.removeOne,
  },
});

export const {
  selectAll: selectAllPosts,
} = postsAdapter.getSelectors((state) => state.posts);

RTK Query

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

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: (builder) => ({
    getUser: builder.query({
      query: (id) => `user/${id}`,
    }),
  }),
});

export const { useGetUserQuery } = api;

Using Redux with React

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';

ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);

Best Practices

  • Use Redux Toolkit for new projects.
  • Keep state normalized and avoid deeply nested structures.
  • Don’t mutate state directly outside of Immer.
  • Split logic by feature using slices.
  • Use memoized selectors with createSelector.
  • Use local component state for transient UI values.

Common Mistakes

  • Mutating state directly in reducers.
  • Dispatching actions from reducers (not allowed).
  • Storing non-serializable values in state.
  • Forgetting to wrap the app with Provider.

Redux vs Redux Toolkit

FeatureClassic ReduxRedux Toolkit
BoilerplateHighLow
Thunk MiddlewareManualBuilt-in
ImmutabilityManualVia Immer
DevToolsManual SetupAutomatic
Code SplitManualSlices

TypeScript with Redux

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

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

export type RootState = ReturnType;
export type AppDispatch = typeof store.dispatch;

Real-World App Structure

  • /features – Slice-based feature folders
  • /app/store.js – Store configuration
  • /api – RTK Query endpoints
  • /components – Reusable UI components

Conclusion

Redux is a powerful tool for managing global state. Redux Toolkit makes it easier and less error-prone. Use slices, selectors, thunks, and RTK Query to manage your data cleanly and efficiently.

Post a Comment (0)
Previous Post Next Post