Skip to main content

Redux

Redux

What is Redux?

Redux is a predictable state container for JavaScript applications. It helps you manage the global state of your application in a way that makes it easier to track changes, debug, and test. Redux is commonly used with React but can be used with any other view library.

Core Concepts

Store

The Redux store is a single JavaScript object that holds the application's state. There is only one store in a Redux application.

Actions

Actions are plain JavaScript objects that describe what happened in the application. They are the only way to send data from your application to your Redux store.

Reducers

Reducers are functions that take the current state and an action as arguments and return a new state. They are pure functions, meaning they don't have side effects and don't modify the input state.

Basic Principles

  1. Single Source of Truth: The state of your whole application is stored in a single object tree within a single store.
  2. State is Read-Only: The only way to change the state is to emit an action, an object describing what happened.
  3. Changes are Made with Pure Functions: To specify how the state tree is transformed by actions, you write pure reducers.

Basic Usage

Installation

npm install redux react-redux

Creating a Store

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

const store = createStore(rootReducer);

Creating Actions

export const increment = () => {
return {
type: 'INCREMENT'
};
};

Creating Reducers

const counter = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
};

Connecting React Components

import React from 'react';
import { connect } from 'react-redux';
import { increment } from './actions';

const Counter = ({ count, increment }) => (
<div>
<button onClick={increment}>Increment</button>
<p>{count}</p>
</div>
);

const mapStateToProps = state => ({
count: state.counter
});

const mapDispatchToProps = {
increment
};

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

Middleware

Redux middleware provides a third-party extension point between dispatching an action and the moment it reaches the reducer. Popular middleware includes Redux Thunk for asynchronous actions and Redux Saga for managing side effects.

DevTools

Redux DevTools allow you to inspect every state and action payload, travel back in time, and see how your application state would change by replaying the actions.

Why Use Redux?

  1. Predictable State: Redux makes the state predictable by imposing certain restrictions on how and when updates can happen.
  2. Debugging: With tools like Redux DevTools, it's easier to debug state changes and view diffs between states.
  3. Middleware: Easily extend Redux with custom middleware.
  4. Community and Ecosystem: Redux has a large community, which means more tutorials, more resources, and more third-party extensions.

Drawbacks

  1. Boilerplate: Redux requires a lot of boilerplate code.
  2. Complexity: The pattern of dispatching actions and updating state via reducers can be hard to grasp for beginners.

Redux is a powerful tool for managing state in large, complex applications, but it can be overkill for simpler apps. Always evaluate whether you need Redux or if the built-in React state management is sufficient for your project.

Reduxjs/toolkit

What is @reduxjs/toolkit?

@reduxjs/toolkit is the official, opinionated, batteries-included toolset for efficient Redux development. It was developed to help simplify common Redux use cases and reduce boilerplate code. The toolkit includes utilities to simplify actions, reducers, and store configuration.

Key Features

createSlice

This function automatically generates action creators and action types based on the reducers you provide. It returns an object containing the reducer function and the generated action creators.

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

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

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

createAsyncThunk

This function simplifies handling of async logic in Redux. It dispatches pending, fulfilled, and rejected action types automatically.

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

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

configureStore

This function sets up the Redux store with good defaults for development and production. It automatically combines reducers, adds middleware, and enables the Redux DevTools Extension.

import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';

const store = configureStore({
reducer: rootReducer,
});

export default store;

createAction and createReducer

These are lower-level utilities for creating actions and reducers with less boilerplate.

import { createAction, createReducer } from '@reduxjs/toolkit';

export const increment = createAction('counter/increment');
export const decrement = createAction('counter/decrement');

const counterReducer = createReducer(0, {
[increment]: (state) => state + 1,
[decrement]: (state) => state - 1,
});

Advantages

  1. Less Boilerplate: The toolkit reduces the amount of boilerplate code you have to write.
  2. Opinionated Setup: Provides a set of sensible defaults to help you get started quickly.
  3. Type Safety: Built with TypeScript support in mind, making it easier to write type-safe Redux code.
  4. DevTools Integration: Automatically integrates with Redux DevTools for easier debugging.

How to Install

You can install it via npm or yarn:

npm install @reduxjs/toolkit
# OR
yarn add @reduxjs/toolkit

Best Practices

  1. Immutable Updates: Always follow the "immutable update" pattern when writing reducers.
  2. Colocate Logic: Keep the logic for each slice of state in a single "slice file" when possible.
  3. Normalize State: Use a normalized state shape for better performance and easier updates.

@reduxjs/toolkit is a powerful library that simplifies Redux development, making it more efficient and enjoyable. It's highly recommended for both beginners and experienced Redux developers.

useSelector hook

What is useSelector Hook?

The useSelector hook is a part of the react-redux library, which is used for accessing the Redux store's state within React functional components. This hook replaces the need for the connect higher-order component (HOC) when you only need to read state and not dispatch actions.

Basic Usage

The useSelector hook takes a selector function as its argument. The selector function receives the entire Redux store state as its only argument and returns the part of the state that the component needs.

Here's a simple example:

import React from 'react';
import { useSelector } from 'react-redux';

const MyComponent = () => {
const counter = useSelector((state) => state.counter);

return (
<div>
<h1>Counter Value: {counter}</h1>
</div>
);
};

In this example, the useSelector hook retrieves the counter value from the Redux store's state.

When Does useSelector Cause a Re-render?

The component will re-render whenever the value returned by the selector function changes. By default, useSelector uses strict reference equality (===) to compare the previous and current values. If they are different, the component re-renders.

Using Multiple useSelector Hooks

You can use multiple useSelector hooks in a single component to select different slices of the state:

const counter = useSelector((state) => state.counter);
const user = useSelector((state) => state.user);

Custom Comparisons

You can also provide a second argument to useSelector to specify how the selected state is compared with the previous state. This is useful for optimizing performance.

import { shallowEqual } from 'react-redux';

const data = useSelector((state) => state.data, shallowEqual);

In this example, shallowEqual performs a shallow comparison between the previous and current state, avoiding unnecessary re-renders.

Advantages

  1. Simplicity: It simplifies the code by removing the need for mapStateToProps.
  2. Optimization: It only causes re-renders when the selected part of the state changes.
  3. Flexibility: You can use multiple useSelector hooks in a single component for better code organization.

Best Practices

  1. Colocation: Keep the useSelector close to where the selected state is used for better readability.
  2. Memoization: Use memoized selectors to avoid unnecessary computations and re-renders.

The useSelector hook provides a more straightforward and optimized way to access Redux state in functional components, making it easier to build and maintain React-Redux applications.