Web Unit 3 Sprint 10 - Redux & State Management

Module 3: Redux Toolkit

In this module, you'll learn about Redux Toolkit, a powerful library that simplifies Redux development. You'll explore how to create slices, handle async actions with Redux Thunk, and implement middleware in your Redux store.

Learning Objectives

Content

What is Redux?

Understanding Redux

Redux is a predictable state management library for JavaScript applications and is the most popular state container for React applications. As applications grow in size and complexity, managing state becomes increasingly challenging. Redux provides a solution to this problem.

The core concepts and principles of Redux are three-fold:

The Store

Everything that changes within your application is represented by a single JavaScript object known as the store. The store contains the entire state for your application.

Application State is Immutable

When the application state changes, we clone the state object, modify the clone, and replace the original state with the new copy. We never mutate the original object, and we never write directly to our store object.

Pure Functions Change Our State

Given the same input, a pure function returns the same output every time. All functions (reducers) in Redux must be pure functions. They take in some state and a description of what changes took place (actions) and return a copy of our state.

Redux creates a more predictable state management flow, especially useful in larger applications where state management can become complex.

Starting Out with Redux and React

Integrating Redux with React

To use Redux with React, you need to set up a few key components:

  1. Store: The central state repository
  2. Reducers: Functions that specify how state changes in response to actions
  3. Actions: Objects that describe what happened in the application
  4. Provider: React component that makes the Redux store available to the entire component tree

Basic setup for Redux in a React application:

// Create a reducer
const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

// Create a store
import { createStore } from 'redux';
const store = createStore(counterReducer);

// Connect Redux to React
import { Provider } from 'react-redux';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

With this setup, any component in your application can connect to the Redux store and access or update the state.

Working with Reducers and Actions

Reducers and Actions

The Redux flow follows a specific pattern:

  1. An action is dispatched (usually from a user interaction)
  2. The reducer processes the action and returns a new state
  3. The store updates its state with the value returned by the reducer
  4. UI components connected to the store update to reflect the new state

Action Creators

Action creators are functions that create and return action objects. They make it easier to dispatch actions:

// Action Types
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// Action Creators
const increment = () => ({
  type: INCREMENT
});

const decrement = () => ({
  type: DECREMENT
});

// Dispatch in a component
dispatch(increment());

Connecting Components

With the useSelector and useDispatch hooks from React-Redux, you can connect components to the Redux store:

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

function Counter() {
  // Get data from store
  const count = useSelector(state => state.count);
  // Get the dispatch function
  const dispatch = useDispatch();
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

Redux Thunk

Handling Asynchronous Actions with Redux Thunk

By default, Redux action creators cannot contain side effects or asynchronous logic. Redux Thunk is middleware that allows you to write action creators that return a function instead of an action. This function can perform asynchronous operations and dispatch actions when ready.

Setting up Redux Thunk:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

Creating an asynchronous action creator with Redux Thunk:

// Action Types
const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST';
const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS';
const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE';

// Action Creators
const fetchUsers = () => {
  return async (dispatch) => {
    // Dispatch request action
    dispatch({ type: FETCH_USERS_REQUEST });
    
    try {
      // Make API call
      const response = await fetch('https://api.example.com/users');
      const data = await response.json();
      
      // Dispatch success action with data
      dispatch({ 
        type: FETCH_USERS_SUCCESS, 
        payload: data 
      });
    } catch (error) {
      // Dispatch failure action with error
      dispatch({ 
        type: FETCH_USERS_FAILURE, 
        payload: error.message 
      });
    }
  };
};

Redux Thunk enables you to handle complex asynchronous workflows in your Redux applications, such as API requests, while maintaining the predictable state management that Redux provides.

Practice Activities

Additional Resources