Categories
Redux

Introduction to State Management with Redux

With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it’s also a popular state management solution for React apps when combined with React-Redux.

In this article, we’ll look at how to create a basic store in the app and use it.

Installation

Redux is available as an NPM package. We can install it by running:

npm install --save redux

with NPM or:

yarn add redux

with Yarn.

Basic Example

We can use redux to store the state of an app by creating a store and reducer.

Then we can subscribe to the store to get the data and dispatch actions to make changes to the state.

For example, we can write:

import { createStore } from "redux";

function counter(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + action.payload || 1;
    case "DECREMENT":
      return state - action.payload || 1;
    default:
      return state;
  }
}

let store = createStore(counter);
store.subscribe(() => console.log(store.getState()));

store.dispatch({ type: "INCREMENT", payload: 2 });
store.dispatch({ type: "DECREMENT", payload: 1 });

In the code above, we have the counter reducer function which gets the action type and payload and then applies it accordingly.

The counter function checks for action type INCREMENT and DECREMENT and return the new state with the payload added to it.

Then we create the store by running:

let store = createStore(counter);

After that, we can subscribe to the store by calling subscribe on the store and then call getState within the callback for it as follows:

store.subscribe(() => console.log(store.getState()));

Then we can dispatch actions to the store by calling dispatch on it with an object with the type and payload , which will be passed into the counter function in the action parameter.

Therefore, the state will be updated by the action since for the first dispatch call:

store.dispatch({ type: "INCREMENT", payload: 2 });

The type will be passed in as action.type and payload will be set as action.payload to counter .

action.type is ‘INCREMENT’ and action.payload is 2.

Likewise, action.type is ‘DECREMENT’ and action.payload is 1 in the second call.

Note that we don’t mutate the state directly in counter . We return a new state by deriving it from the existing state.

Three Principles of Redux

Three principles apply to the way Redux is built.

Single Source of Truth

Redux stores state in a single place. This makes getting data easy since we only have to get it in one place.

Debugging is also easy because of that. This makes development faster.

Some functionality that was hard to implement now can be easy since now the state is stored in a single tree.

State is Read-Only

The state is read-only so it can’t be changed by accident. We have to express an intent to change the state.

There aren’t subtle race conditions to watch out for since all changes are centralized and happen one by one in strict order.

Since actions are just plain objects, they can be logged, serialized, stored and replayed for debugging and testing purposes.

Changes are Made With Pure Functions

Reducers are pure functions that take the previous state and action and return the next state. We return new state objects instead of mutation previous state.

We can start with a single reducer and then split it off into smaller reducers that management other parts of the state tree.

We can control how they’re called, pass additional data, or make reusable reducers.

Flux Architecture

Redux concentrates all model update logic in one layer of an app. This is one part of the Flux architecture that’s adopted by Redux.

Redux doesn’t have a dispatcher. It relies on pure functions instead of event emitters to change state.

Pure functions are easy to compose and doesn’t need extra management.

Redux assumes that we never mutate our data. We should always return new objects. This is easy now that we have the spread operator for objects.

Photo by Kelly Sikkema on Unsplash

RxJS

We can turn a Redux store into an Rxjs Observable by subscribing to it and calling nexr to return the latest state.

For example, we can write:

import { createStore } from "redux";

function counter(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + action.payload || 1;
    case "DECREMENT":
      return state - action.payload || 1;
    default:
      return state;
  }
}

let store = createStore(counter);

function toObservable(store) {
  return {
    subscribe({ next }) {
      const unsubscribe = store.subscribe(() => next(store.getState()));
      next(store.getState());
      return { unsubscribe };
    }
  };
}

const counterObservable = toObservable(store);

counterObservable.subscribe({
  next(count) {
    console.log(count);
  }
});

store.dispatch({ type: "INCREMENT", payload: 2 });
store.dispatch({ type: "DECREMENT", payload: 1 });

In the code above, we have:

function toObservable(store) {
  return {
    subscribe({ next }) {
      const unsubscribe = store.subscribe(() => next(store.getState()));
      next(store.getState());
      return { unsubscribe };
    }
  };
}

const counterObservable = toObservable(store);

counterObservable.subscribe({
  next(count) {
    console.log(count);
  }
});

store.dispatch({ type: "INCREMENT", payload: 2 });
store.dispatch({ type: "DECREMENT", payload: 1 });

The toObservable returns an Observable which we can subscribe to since it returns a subscribe method.

The Redux store’s subscribe method returns an unsubscribe function so we can unsubscribe to it when we no longer need it.

Then when we call dispatch above, we’ll get the new values logged in the next function above.

Conclusion

We can store an app’s data in a central place with Redux. To do this, we create a store and then subscribe to it to get the latest state from it.

Then we call dispatch on the store with a plain object with the action type and payload to change the store to the latest state.