Understanding Redux Tutorial Examples: A Complete Guide

Master Redux with practical examples. Learn core concepts--store, actions, reducers--and how Redux Toolkit simplifies state management in JavaScript applications.

What is Redux and Why Use It

Redux is a pattern and library for managing and updating global application state, where the UI triggers events called "actions" to describe what happened, and separate update logic called "reducers" updates the state in response. It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion.

The patterns and tools provided by Redux make it easier to understand when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur. Redux guides you toward writing code that is predictable and testable.

When Should You Use Redux?

Redux helps you deal with shared state management, but like any tool, it has tradeoffs. Redux is more useful when:

  • You have large amounts of application state that are needed in many places in the app
  • The app state is updated frequently over time
  • The logic to update that state may be complex
  • The app has a medium or large-sized codebase, and might be worked on by many people

Not all apps need Redux. Take some time to think about the kind of app you're building, and decide what tools would be best to help solve the problems you're working on.

For teams building modern web applications, understanding state management patterns like Redux is essential for creating maintainable, scalable codebases. When combined with proper web development practices, Redux helps teams build applications that are easier to debug, test, and extend over time.

Core Redux Concepts Explained Through Examples

The Store: Your Application's Single Source of Truth

The Redux store is the heart of any Redux application. It holds the complete state tree of your application and provides methods to access the state, dispatch actions, and register listeners.

import { createStore } from 'redux';
import counterReducer from './reducers/counter';

const store = createStore(counterReducer);

// Access current state
console.log(store.getState()); // { counter: 0 }

// Subscribe to state changes
store.subscribe(() => {
 console.log('State changed:', store.getState());
});

The store is created by passing in a reducer function, and has a method called getState() that returns the current state value.

Actions: Describing What Happened

Actions are plain JavaScript objects that describe what happened in your application. They are the only source of information for the store. By convention, actions should have a type field that indicates what type of action was performed.

// Simple action
const incrementAction = {
 type: 'counter/increment'
};

// Action with payload
const addTodoAction = {
 type: 'todos/addTodo',
 payload: {
 id: 1,
 text: 'Learn Redux',
 completed: false
 }
};

// Action creator function
function increment() {
 return { type: 'counter/increment' };
}

Reducers: Updating State Based on Actions

A reducer is a pure function that takes the current state and an action, and returns the new state. Reducers must follow specific rules: they should only calculate the new state based on the state and action arguments, they must not modify the existing state, and they must be pure functions.

const initialState = { count: 0 };

function counterReducer(state = initialState, action) {
 switch (action.type) {
 case 'counter/increment':
 return { ...state, count: state.count + 1 };
 case 'counter/decrement':
 return { ...state, count: state.count - 1 };
 case 'counter/incrementByAmount':
 return { ...state, count: state.count + action.payload };
 default:
 return state;
 }
}

Dispatch: Triggering State Changes

The only way to update the state is to call store.dispatch() and pass in an action object.

store.dispatch({ type: 'counter/increment' });
console.log(store.getState()); // { count: 1 }

store.dispatch(increment());
console.log(store.getState()); // { count: 2 }

Understanding these core concepts is fundamental to mastering JavaScript development and building robust applications.

Redux Toolkit: The Modern Way to Use Redux

The Redux Toolkit package is intended to be the standard way to write Redux logic. It was originally created to help address three common concerns about Redux: configuring a Redux store is too complicated, having to add many packages to get Redux to do anything useful, and Redux requiring too much boilerplate code.

Installation

npm install @reduxjs/toolkit react-redux

configureStore: Simplified Store Setup

configureStore() wraps createStore to provide simplified configuration options and good defaults. It can automatically combine your slice reducers, adds whatever Redux middleware you supply, includes redux-thunk by default, and enables use of the Redux DevTools Extension.

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import todoReducer from './todoSlice';

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

createSlice: Compact Reducer and Action Creation

createSlice() accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types.

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;

createAsyncThunk for Async Operations

createAsyncThunk accepts an action type string and a function that returns a promise, and generates a thunk that dispatches pending/fulfilled/rejected action types based on that promise.

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

Redux Toolkit represents the modern approach to JavaScript state management, reducing boilerplate while maintaining the predictable architecture that makes Redux powerful.

Using Redux with React

React-Redux is the official package that lets your React components interact with a Redux store by reading pieces of state and dispatching actions to update the store.

Provider Component

import { Provider } from 'react-redux';
import { store } from './store';

function Root() {
 return (
 <Provider store={store}>
 <App />
 </Provider>
 );
}

useSelector Hook: Reading State

import { useSelector } from 'react-redux';

function Counter() {
 const count = useSelector((state) => state.counter.value);
 return <p>Count: {count}</p>;
}

useDispatch Hook: Dispatching Actions

import { useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';

function CounterControls() {
 const dispatch = useDispatch();
 
 return (
 <div>
 <button onClick={() => dispatch(increment())}>Increment</button>
 <button onClick={() => dispatch(decrement())}>Decrement</button>
 </div>
 );
}

When building React applications, properly integrating state management is crucial for maintaining clean, predictable code that scales effectively with your application's complexity.

RTK Query: Data Fetching Made Simple

RTK Query is provided as an optional addon within the @reduxjs/toolkit package. It is purpose-built to solve the use case of data fetching and caching, supplying a compact, but powerful toolset to define an API interface layer for your app.

Creating an API Slice

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

export const api = createApi({
 reducerPath: 'api',
 baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
 endpoints: (builder) => ({
 getPosts: builder.query({ query: () => '/posts' }),
 getPostById: builder.query({ query: (id) => `/posts/${id}` }),
 createPost: builder.mutation({
 query: (newPost) => ({
 url: '/posts',
 method: 'POST',
 body: newPost,
 }),
 }),
 }),
});

export const {
 useGetPostsQuery,
 useGetPostByIdQuery,
 useCreatePostMutation
} = api;

Using Query Hooks in Components

import { useGetPostsQuery } from './services/api';

function PostsList() {
 const { data: posts, error, isLoading } = useGetPostsQuery();

 if (isLoading) return <div>Loading...</div>;
 if (error) return <div>Error: {error.message}</div>;

 return (
 <ul>
 {posts.map((post) => (
 <li key={post.id}>{post.title}</li>
 ))}
 </ul>
 );
}

RTK Query eliminates the need for manual data fetching and caching logic, making your web application API integrations cleaner and more maintainable while providing automatic caching and background updates.

Key Redux Best Practices

Keep State Normalized

Normalize nested data structures to avoid deep updates and make lookups efficient.

Use Selectors for Derived Data

Create selectors to compute derived data rather than computing it in components.

Organize by Feature

Structure your Redux code around features rather than file types.

Use Redux DevTools

Leverage time-travel debugging and action history for easier debugging.

Summary

Redux provides a powerful pattern for managing complex application state in a predictable way. While it adds some initial complexity compared to local component state, the benefits of predictability, debuggability, and maintainability make it an excellent choice for medium to large applications.

The introduction of Redux Toolkit has significantly simplified Redux usage by reducing boilerplate, providing sensible defaults, and including powerful features like RTK Query for data fetching. For new projects, Redux Toolkit is the recommended approach, while understanding the core concepts remains valuable for debugging and advanced use cases.

Whether you choose vanilla Redux or Redux Toolkit, the fundamental principles remain the same: actions describe what happened, reducers update state based on those actions, and the store holds everything together in a single source of truth.

Need help implementing Redux in your project? Our web development team has extensive experience building scalable applications with modern state management patterns. We can help you integrate Redux effectively or explore whether AI automation solutions might complement your application's architecture.

Frequently Asked Questions

Ready to Build Better Web Applications?

Our team of experienced developers can help you implement state management solutions that scale with your business needs.