Redux is a framework for managing the state of JavaScript applications. In React state is managed by components, and passed down the component tree via props. This works well for small applications, but becomes difficult to manage for larger applications. Redux provides a central store for the state of the application.

Principles

Redux is based on three principles:

  • Single source of truth: The state of the whole application is stored in an object tree within a single store.
  • State is read-only: The only way to change the state is to emit an action, an object describing what happened.
  • Changes are made with pure functions: To specify how the state tree is transformed by actions, you write pure reducers.

Data Flow

The data in a Redux application follows a unidirectional flow. Views emit events which are converted to actions. Actions are dispatched to invoke reducers, which modify the state. Views are updated when the state changes.

graph LR
    A[Action]
    D[Dispatcher]
    R[Reducer]
    S[State]
    V[View]

    A -->|action| D
    S -->|state| V
    V -->|event| A
    subgraph Store
        D -->|action| R
        R --> S
        S -->|state| R
    end

Example

The following example is a simple counter application that uses Redux to manage the state.

  const redux = require("redux");
// initial state
const initialState = { count: 0 };
// reducer
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "INCREMENT": return { count: state.count + 1 };
    case "DECREMENT": return { count: state.count - 1 };
    default: return state;
  }
};
// create store from reducer
const store = redux.createStore(reducer);
// simulate view (subscribe to store)
store.subscribe(() => console.log(store.getState()));
// dispatch actions
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });
  

Middleware

Middleware provides a third-party extension point between dispatching an action and the moment it reaches the reducer. It can be used for logging, crash reporting, performing asynchronous tasks, etc.

Example

The following example shows a simple logger middleware.

  const redux = require("redux");
// initial state
const initialState = { count: 0 };
// reducer
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "INCREMENT": return { count: state.count + 1 };
    case "DECREMENT": return { count: state.count - 1 };
    default: return state;
  }
};
// logger middleware
const logger = store => next => action => {
  console.log("dispatching", action);
  const result = next(action);
  console.log("next state", store.getState());
  return result;
};
// create store from reducer
const store = redux.createStore(reducer, redux.applyMiddleware(logger));
// simulate view (subscribe to store)
store.subscribe(() => console.log(store.getState()));
// dispatch actions
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });
  

Async Actions

Actions are normally plain objects. Middleware can be used to dispatch functions, which are called with the dispatch and getState functions as arguments. This is useful for performing asynchronous tasks.

In general, async actions usually dispatch three different kinds of actions:

  • An action informing the reducers that the request began.
  • An action informing the reducers that the request finished successfully.
  • An action informing the reducers that the request failed.

Example

The following example simulates an asynchronous action.

  const redux = require("redux");
// initial state
const initialState = { count: 0,  waiting: false };
// reducer
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "INCREMENT": return { count: state.count + 1 };
    case "DECREMENT": return { count: state.count - 1 };
    case "WAITING": return { ...state, waiting: true };
    case "RECEIVED": return { ...state, waiting: false };
    default: return state;
  }
};
// create store from reducer
const store = redux.createStore(reducer, redux.applyMiddleware(thunk));
// create async action
const incrementAsync = () => {
  return dispatch => {
    dispatch({ type: "WAITING" });
    setTimeout(() => {
      dispatch({ type: "INCREMENT" })
      dispatch({ type: "RECEIVED" });
    }, 1000);
  };
};
// simulate view (subscribe to store)
store.subscribe(() => console.log(store.getState()));
// dispatch action (first waiting, after 1 second increment)
store.dispatch(incrementAsync());
  

Reducers

Reducers reduce a sequence of actions into a state. They are, in a sense, like accumulator functions.

For bigger applications, it is recommended to split the root reducer into smaller reducers that manage specific parts of the state tree. The combineReducers() helper function can be used to combine several reducers into one.

Example

The following example shows how to use combineReducers().

  const redux = require("redux");
// initial state
const initialState = { count: 0, waiting: false };
// counter reducer
const counterReducer = (state = initialState.count, action) => {
  switch (action.type) {
    case "INCREMENT": return state + 1;
    case "DECREMENT": return state - 1;
    default: return state;
  }
};
// waiting reducer
const waitingReducer = (state = initialState.waiting, action) => {
  switch (action.type) {
    case "WAITING": return true;
    case "RECEIVED": return false;
    default: return state;
  }
};
// root reducer
const rootReducer = redux.combineReducers({
  count: counterReducer,
  waiting: waitingReducer
});
// create store from reducer
const store = redux.createStore(rootReducer);
// ...
  

React

React bindings for Redux are available as the react-redux package. The hooks can be found on the React Redux Hooks documentation page.

Using Context

The react-redux package uses the React Context API to provide the store to the components. The following example shows how to use the Context API directly.

Zustand

https://www.npmjs.com/package/zustand is a small, fast and scalable state management library for React.