Register to get access to free programming courses with interactive exercises

Redux JS: Redux (React)

Redux is somewhat like a database in the program. Inside itself, it stores the application data or the global state. Redux is only responsible for the data and has nothing to do with the browser, the DOM, or the front end. Theoretically, we can use Redux even for backend development in Node.js.

From the code's perspective, Redux is an object that contains data. The rest of the application uses it to store, modify, and retrieve data. In Redux terminology, it's called a store because the data are stored internally.

In the simplest case, an ordinary JavaScript object would be suitable for solving such a problem:

// An example of the global state and its initialization
const state = { posts: [], activePostId: null, categories: [] };

// Somewhere inside the application
state.posts.push(/* new post */);

// Somewhere else
state.posts.map(/* processing logic */);

However, this approach does not allow you to track data changes. If some part of the application has changed the data, then we won't know about it, which means we won't be able to react, for example, by rerendering the necessary part of the screen. Redux solves this problem.

Changing the data inside the store generates events that you can subscribe to and execute any logic, usually redrawing the screen. It works like this because we change the data inside Redux by indicating actions, not directly like with other objects.

Below is a complete example using Redux:

import { createStore } from 'redux';

// A reducer is a function that describes how the data inside the store changes
// It takes the current state of the application as input and should return the new one
// This is how the reducer function works, hence the name, but it can be anything
// The second parameter describes the action. We can use it to find out how exactly to update the data for a specific call
//An action is an object that must have a type field containing the name of the action
const reducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default: //The default action is to return the current state
      return state;
  }
};

// Creating a store based on the reducer
// The reducer returns the state in this store
const store = createStore(reducer);

// The state can be retrieved using the getState() function
store.getState(); // 0 - since this is the initial value of the state

// The subscribe function allows you to subscribe to a state change inside the store
// It's very similar to addEventListener, but without specifying an event
// As soon as any part of the state changes, the store calls everything that was added or changed in succession
// We fetch the state and print it to the screen
store.subscribe(() => console.log(store.getState()d));

// A dispatch is a function that calls the reducer

// The reducer increments the state by one
store.dispatch({ type: 'INCREMENT' }); // 1
// The reducer increments the state by one
store.dispatch({ type: 'INCREMENT' }); // 2
// The reducer reduces the state by one
store.dispatch({ type: 'DECREMENT' }); // 1

store.getState(); // 1

// To avoid duplication and increase the level of abstraction, we'll move the actions into functions
const increment = () => ({ type: 'INCREMENT' });
const decrement = () => ({ type: 'DECREMENT' });

store.dispatch(increment()); // 2
store.dispatch(decrement()); // 1

The only way to make state changes to the store is to pass/send an action to the dispatch() function.

An action is a regular JavaScript object with at least one property – type. There are no restrictions imposed on the contents of this property. The main thing is that the reducer has a suitable handler in the switch.

We describe the process of changing the state inside the reducer. Outside we only say what change to perform. This approach is very different from what we did in React, where the state change happens directly:

Sending an action (Redux)

Let's look at another example using an array and passing data through an action:

// payload - a property that stores data
const addUser = (user) => ({ type: 'USER_ADD', payload: { user } });

const reducer = (state = [], action) => { // initializing state
  switch (action.type) {
    case 'USER_ADD': {
      const { user } = action.payload; // data
      // so new data are returned in the reducer without changing the old data
      return [...state, user];
    }
    case 'USER_REMOVE': {
      const { id } = action.payload; // data
      return state.filter(u => u.id !== id); // the data don't change
    }
    default:
      return state;
  }
};

const user = /* ... */;
const store = createStore(reducer);
store.dispatch(addUser(user));

Even though the payload key is optional, and you can put all the data directly into the action, we highly recommend not doing that. Mixing statically defined keys with dynamic ones in one object is a bad idea. In future lessons, we'll use libraries that require us to work this way.

How to set up Redux

It only takes seven lines to write the simplest version of Redux. Here they are:

// The second parameter is the initial state of the data inside the store
const createStore = (reducer, initialState) => {
  let state = initialState;
  return {
    dispatch: action => { state = reducer(state, action) },
    getState: () => state,
  }
}

Initial state

We have already mentioned that we set the initial state is set in the definition of the reducer:

const reducer = (state = 0, action) => { /* ... */ }

But this isn't enough sometimes. The data may come from the backend, so we should load it into the store before working with it. There's something you can do in Redux in this case:

// The second parameter is the initial state when creating the repository
const store = createStore(reducer, initState);
// @@redux/INIT

Redux dispatches a specific action that we cannot intercept. If the reducer is implemented correctly and contains a default section in the switch, the store will be filled with data from initState:

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

const store = createStore(reducer, 100);

store.getState(); // 100

In the code above, the createStore() function will call the reducer this way: reducer(100, '@@redux/INIT'). Then we will execute the default branch, and the state will be 100.

Three principles

Let us summarize the main things about Redux:

  • Single source of truth. When we use Redux, we work with only one store per application. The whole state is in one place
  • The state is read-only. The only way to change the state is to send an action inside the store
  • Changes are made with pure functions. Inside the repository, we can use only pure functions to be able to time travel

Recommended materials

  1. Redux Core Principles

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

Bookmate
Health Samurai
Dualboot
ABBYY
Suggested learning programs
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.