Register to get access to free programming courses with interactive exercises

Redux JS: Redux (React)

Redux is somewhat like a database in the program. It stores the application data (or as they call it, the global state) inside itself. Redux is only responsible for the data and has nothing to do with the browser, the DOM, or the frontend in general. Theoretically, Redux can even be used for backend development in Node.js.

Redux, from the code's perspective, is an object that contains data. It is used by the rest of the application 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 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). This is achieved because the data inside Redux isn't changed directly, as in the case of a regular object, but by indicating "actions".

Below is a complete example using Redux:

import { createStore } from 'redux';

// 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,
// store calls everything that was added or changed in succession
// Here we just fetch the state and print it to the screen
store.subscribe(() => console.log(store.getState()));

// 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 JS object with at least one property – type. No restrictions are imposed on the contents of this property, the main thing is that the reduce has a suitable handler inside it (in switch).

The process of changing the state itself is described inside the reducer, and outside we only say what change needs to be performed. 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));

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

How Redux is set up

It only takes 7 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

It was mentioned above that the initial state is set in the definition of the reducer:

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

But often this isn't enough. The data may come from the backend and need to be loaded into the store before you can begin 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 special action that cannot be intercepted. If the reducer is implemented correctly and contains a default section in the switch, then the store will be filled with data from initState. Example:

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 like this: reducer(100, '@@redux/INIT'). Then the default branch will be executed, and the store's state will be the number 100.

Three principles

To summarize. What's the main thing 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 - only pure functions can be used inside the repository, which is what allows us to time travel.

Recommended materials

  1. Redux Core Principles

Hexlet Experts

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

The Hexlet support team or other students will answer you.

About Hexlet learning process

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:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs

From a novice to a developer. Get a job or your money back!

Frontend Developer icon
Profession
beginner
Development of front-end components for web applications
start anytime 10 months

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.