DEV Community

Cover image for This is Redux, in plain English
Geoffrey Siele
Geoffrey Siele

Posted on • Edited on

This is Redux, in plain English

Photo by Jake Hills on Unsplash

We have the View, Action, Store, and Reducer.
And yeah, those are the building blocks of Redux.

In a nutshell:
Redux is a state management library that scales well even for large applications. Well, that's what it excels at, and was made for.

Official Redux intro:
Redux is: Predictable, Centralized, Debuggable, Flexible.
Redux is a predictable state container for JavaScript apps.

It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.

You can use Redux together with React, or with any other view library. It is tiny (2kB, including dependencies), but has a large ecosystem of addons available.

A side-note on Redux:
REDUX IS A REALLY MARKETABLE SKILL TO POSSESS as it solves an apparent need for scalable, stress-free management of state in large, real-world applications.

Quick facts about Redux:
Redux is based on the Flux pattern but it has its unique characteristics.
Redux also follows the unidirectional data flow pattern.

Before we jump into how things piece together and work under the hood, let's explore the view.

VIEW:
To start off, the view is basically the user interface that facilitates user interaction with your application. User interface elements are wired up with their respective event handlers. Whenever an event is fired on the UI element, the event handler is also responsible for calling the dispatch method on the store object thereby relaying (i.e. dispatching) the corresponding action object to the store.

store.dispatch({ type: 'INCREMENT_NUM', payload: 3 });
Enter fullscreen mode Exit fullscreen mode

Just in case you've been wondering...
Redux can be used as a data store for any UI layer. The most common usage is with React and React Native, but there are bindings available for Angular, Vue, Mithril, and more. It is most useful when combined with a declarative view implementation that can infer the UI updates from the state changes, such as React or one of the similar libraries available.

Well, Redux is just a combination of things we are already familiar with; in a systematic manner.

--Things we know--
* We know that we can declare objects using the object literal notation as we do below.
* We also know that one way of accessing object property values is using the dot notation.

const addThree = { type: 'INCREMENT_NUM', payload: 3 };
console.log(addThree.type) // 'INCREMENT_NUM'
console.log(addThree.payload) // 3
Enter fullscreen mode Exit fullscreen mode

Looking at the addThree object above,
addThree.type would result in 'INCREMENT_NUM',
and addThree.payload would yield 3.

* We also know that an object e.g. { 'topic': 'Redux ', 'key_constituents' : 4 } as a stand-alone is an unnamed object and it's totally valid.

ACTION:
So, an action is just a plain JavaScript object.
An action is often modeled as the objects above, with two properties: a type and a payload.
The type property describes what kind of operation this action instigates.
The payload property (which may be called anything) represents the data on which the operation described is to be performed.

REDUCER:
Redux introduced the Reducer in place of Dispatcher in flux.
Reducers can be NUMEROUS within a single application.
A reducer is a PURE FUNCTION whose only mandate is to update the state.
The reducer accepts two parameters: current_state & an action, updates state based on the action, then returns a new_state.

Key rule: NEVER MUTATE STATE DIRECTLY.**

// Reducer structure:
function reducer(currentState, action) {
  
  // Update state based on action.
  // The operator is just a placeholder for a sign based on action.type


  // Modify respective item based on the payload, 
  // extract everything from the current state, update
  // it with the new modifications, and assign it to a 
  // new object, newState. If currentState is 
  // unmodified, assign it to the newState as it is.
  const newState = action.payload 
  ? {
    ...currentState,
    itemToModify: itemToModify (operator) action.payload
  } 
  : currentState;
  
  // Return newState. Whether the state was modified or 
  // not, it's now newState.
  return newState;

}; // reducer
Enter fullscreen mode Exit fullscreen mode

STORE:
Redux keeps a SINGLE STORE which maintains state for the entire app in a single object tree.
The store encapsulates the reducer; so only the store has access to the reducer or reducers within the application.
The store receives actions from the view via a dispatch function that is also encapsulated within the store.

Redux provides a method called createStore that is used to create a store instance for an application; like this:

const store = createStore(reducer, initialState);
Enter fullscreen mode Exit fullscreen mode

Bear in mind that alongside initialState, createStore also takes in the reducer/s as its argument/s during instantiation.
We explore how the createStore function looks like below.

--Things we know--
* We know that JavaScript functions are first-class citizens or objects. This means that they can be passed into other functions as arguments, assigned to variables, and they can also be returned from functions.
* We also know that closures in JavaScript is a powerful feature that implies that variables defined within a function remain viable and in existence in memory even long after the function within which they were defined has returned a value.

** In the createStore function, we'll see that its property >state< shall remain in existence long after its enclosing function >createStore< has returned a value. i.e.

const store = createStore();
Enter fullscreen mode Exit fullscreen mode

** For the retrieval of the value held in state, take note that state will only be accessible using a method that is provided by createStore >getState< i.e.

store.getState()
Enter fullscreen mode Exit fullscreen mode

If you are curious here's the link to the real createStore

Enough with the theory...show me the code!

// Save the code below to a file called reduxExample.js

// createStore is provided by Redux so you will not need to 
// implement it yourself, but below is how it would look like.
// Bear in mind that this is stripped down and adapted for our use // here.

/**
 * createStore leverages the Factory Pattern to create and 
 * maintain the state tree object for the entire application.
 *
 * @param {function} reducer
 * @param {object} initialState
 * @returns {object} { subscribe, dispatch, getState } a store object.
 */
function createStore (reducer, initialState) {

  // state is private & only accessible within the created store.
  let state = initialState;

  // Array to hold a list of all subscribers (observers).
  const listeners = []; 

  // dispatch receives an action object from the view.
  // It invokes a given reducer passing it the state & action.
  // It assigns state to the value returned by the reducer.
  // INSIDE dispatch IS THE ONLY PLACE WHERE STATE CAN BE 
  // UPDATED (by a reducer).
  const dispatch = action => {
    state = reducer(state, action);

    // Invoke each listener whenever the state changes.
    // This is an implementation of the Observer Pattern to 
    // notify all subscribers of changes to state, real time.
    // The state is now an observable.
    listeners.forEach(listener => listener());
  };


  /**
   * subscribe takes a listener function as argument and adds
   * it to the createStore's private listeners array.
   *
   * @param {function} listener
   */
  const subscribe = listener => listeners.push(listener);

  // getState is the ONLY window into the store.
  // It is a getter that exposes the state.
  // getState IS THE ONLY WAY TO ACCESS THE VALUE OF STATE.
  const getState = () => state;

  // createStore returns an object with subscribe, dispatch 
  // and getState functions/methods to make them accessible to 
  // the outside world.
  return { subscribe, dispatch, getState };

}; // createStore

// Declare our initialState.
const initialState = {
  numTrack: 0
};

function reducer (state, action) {
  switch (action.type) {
    case 'INCREMENT_NUM':
      return {
        ...state,
        numTrack: state.numTrack + action.payload
      }

    case 'DECREMENT_NUM':
      return {
        ...state,
        numTrack: state.numTrack - action.payload
      }

    case 'MULTIPLY_NUM':
      return {
        ...state,
        numTrack: state.numTrack * action.payload
      }

    case 'DIVIDE_NUM':
      return {
        ...state,
        numTrack: state.numTrack / action.payload
      }

    default:
      return state;
  }
}; // reducer

// Instantiate a store for our application.
const store = createStore(reducer, initialState);

// Declare actions.
const add_Three = { type: 'INCREMENT_NUM', payload: 3 };
const sub_Two   = { type: 'DECREMENT_NUM', payload: 2 };
const add_Seven = { type: 'INCREMENT_NUM', payload: 7 };
const mul_Three = { type: 'MULTIPLY_NUM',  payload: 3 };
const sub_Five  = { type: 'DECREMENT_NUM', payload: 5 };
const div_Two   = { type: 'DIVIDE_NUM', payload: 2 };

// Declare a single observer (listener).
const listener1 = () => {
  console.log(`Current state: `, store.getState());
};

// Subscribe the listener to observe any state changes.
store.subscribe(listener1);

store.dispatch(add_Three);
store.dispatch(sub_Two);
// Dispatch an action object on the fly.
store.dispatch({ type: 'INCREMENT_NUM', payload: 7 });
store.dispatch(mul_Three);
store.dispatch(sub_Five);
store.dispatch(add_Seven);
store.dispatch(div_Two);

// Assuming you saved the entire code above this line in
// a file called reduxExample.js ...
// Here's the expected output from running this:
// $ node reduxExample.js

/*

Current state:  { numTrack: 3 }
Current state:  { numTrack: 1 }
Current state:  { numTrack: 8 }
Current state:  { numTrack: 24 }
Current state:  { numTrack: 19 }
Current state:  { numTrack: 26 }
Current state:  { numTrack: 13 }

*/
Enter fullscreen mode Exit fullscreen mode

Top comments (0)