Redux: Basic Concepts and Data Flow

Introduction

Redux is an open-source library for Javascript that manages and updates application state, using events called "actions". It is like a centralised store for the state used across an entire application, with rules that ensure that the updation of state can only be done predictably. It is usually used with libraries like Angular or React to build user interfaces. 

When and why should I use redux?

Redux is quite useful when:

  • There are large amounts of application states required in several places in the app.
  • The application state is frequently updated.
  • The logic for state updation might be complex.
  • The codebase in the application is medium or large and worked on by a lot of people.

 

Redux helps manage the "global" state, which is needed across several parts of the application. The tools and patterns offered by Redux make it convenient to understand how, where and why the state in an application is being updated and how its logic will act after those changes. Redux also guides users towards a more predictable and testable code. 

If you want to get familiar with some basic definitions in Redux, you can visit the Coding Ninjas guide to Redux for a kickstart.

Background Concepts

Below are the terms and concepts that need to be learned for Redux:

State management

Taking an example where the React counter component tracks some number in the component state and increments it when the given button is clicked:

function _counter()
{
  // the state is a counter value
  const [counter, setCounter] = useState(0)

  // the action is when code causes the state to update when something happens
  const increment = () =>
  {
    setCounter(prevCounter => prevCounter + 1)
  }

  // View: the UI definition
  return
  (
    <div>
      Value: {counter} <button
      onClick={increment}>Increment</button>
    </div>
  )
}

The application has:

  • The state that drives this application;
  • The view, which is a description of the User Interface based on the present state
  • The actions are the events that trigger updates in the state and occur in the application based on input.

 

This is a great example of one-way data flow:

  • The state describes how the condition of the application is at a given point in time.
  • Based on that state, the User Interface is rendered.
  • When some action occurs (e.g., the user clicks a button), the state is updated.
  • Based on the new state, the UI re-renders.

 

However, when multiple components need to share and utilise the same state, the simplicity is broken down, mainly if the components are situated in various application parts. This issue can sometimes be resolved by "lifting state up" to parent components, but the method might not always help.

So a method for users to solve this issue is extracting such shared states from the components and putting them outside the component tree at a centralised location. 

The component tree will thus become a big "view", and all components will have access to the state or thus trigger actions, irrespective of the location of the components in the tree.

The basic idea behind Redux is thus a centralised space that can contain the global state in an application and fixed patterns for when the state is being updated so that the code is more predictable.

Immutability

Mutable objects are the ones that are changeable and immutable means something that cannot be changed. Objects and arrays in JavaScript are mutable by default. Users can change the contents of an object’s fields after creating them. The same applies to arrays too:

const _obj = { x1y2 }


// the contents of the object have changed, but it is still the same object outside
obj.y3

const arr = ['x''y']


// Similarly, the contents of array can be changed too
arr.push('z')
arr[1] = 'a'

The above-shown concept is known as mutating the object or array. The memory has the same object or array reference, but the contents inside them will have changed.

The code will have to make copies of the already existing arrays or objects and then modify the copies to update values immutably.

This can be done using the array or object spread operators in JavaScript and also using array methods returning new copies of the array instead of mutating the original array:

const obj =

{
  x: {
      // safe updation of obj.x.z requires us to copy each piece
      z: 3
    },
  y2
}

const obj2 =

{
  // copy obj
  ...obj,
  // overwrite x
  x:

  {
    // copy obj.x
    ...obj.x,
    // overwrite z
    z: 42
  }
}

const arr = ['x''y']
// new copy of arr is created with "z" appended to its end
const arr2 = arr.concat('z')

// or, the program could use a copy of the original array:
const arr3 = arr.slice()


// mutating the copy
arr3.push('z')

All state updates in Redux are expected to be done immutably. 

Redux Terminology

Below are some of the important Redux terms that need to be understood before proceeding further:

Actions

Actions are JavaScript objects with a ‘type’ field and can be considered as events describing something that happened in the app.

The ‘type’ fields should be strings giving such actions some descriptive name, such as "todos/todoAdded". The strings are usually written as "domain/eventName", meaning first the action category and then specifically the thing that happened.

Action objects could have additional information about the event in other fields. Conventionally, this additional information is put in a field known as payload.


An example of a typical action object is given below:

const addTodoAction =

{
  type'todos/todoAdded',
  payload: 'Buy sugar'
}

Reducers

Reducers are functions receiving the present state and action objects. They can be considered event listeners that handle events according to the received action (event). Reducers decide how the state will be updated and return the new state: (state, action) => newState. 

Reducers should always follow these rules:

  • The value of the new state should be calculated only based on the state and action arguments.
  • The existing state must not be modified. Instead, reducers should copy the existing state and make changes to make immutable updates.
  • No other side effects must be caused, and asynchronous logic, calculating random values and similar activities must be avoided.

Reducer function logic follows these steps:

  • Checking whether the reducer cares about this action or not
  • If yes, copy the state, update it with new values, and return it
  • Otherwise, return the existing state.

To decide the new state, reducers could use any logic such as if/else, switch or loops

const initialState = { value: 0 }
function Reducer(state = initialState, action)
{

 

  // Checking whether the reducer cares about this action or not
  if (action.type === 'counter/incremented')
  {


    // Make a copy of `state` if yes
    return
    {
      ...state,


      // updating copy with new value
      value: state.value + 1
    }
  }
  // else, return the existing state
  return state
}

Store

A store is an object where the present state in the Redux application lives. It is created by passing in a reducer and has a method called getState that returns the current state value:

import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}

Dispatch

Dispatching actions can be considered as triggering events in the application.

The dispatch method in the Redux store provides a way to update the state by calling store.dispatch() and passing an Action. The reducer function of the store will run, and the new state value will be saved inside. Then, getState() can be called to retrieve the updated value:

store.dispatch({ type'counter/incremented' })
console.log(store.getState())
// {value: 1}

Selectors

Selector functions can extract specific parts of information from a store state value. As an application grows bigger, this can help avoid repeating logic as different parts of the app need to read the same data:

const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2

 

Source: dev community

 

Redux Principles

Redux is a valuable tool to organise application states and very predictably contains states for JavaScript applications. Its design can be summarised as:

  1. A single store is used to contain the application’s global state, and any data must exist in only one location instead of being duplicated several times. Debugging is thus made easier, along with ease of inspecting app state and centralising logic.
  2. The state can only be changed by dispatching an action. This ensures that the UI doesn’t overwrite data accidentally, and the reason behind the state update is easier to trace. Actions being plain JavaScript objects can be logged, stored, serialized, and replayed later to debug, test and so on.
  3. Reducer functions are used to define how the state tree is updated based on actions. Reducers take the present state and an action and return to the next state. Reducers can be split like other functions, and these smaller functions could be used for writing reducers that are reusable for common tasks.

Redux Application Data Flow

The article briefly discussed unidirectional data flow earlier and the sequence of steps to update the app. These steps can be explained in more detail for Redux:

Initial setup

  • A root reducer function is used to create a Redux store.
  • The return value after the store calls the root reducer is saved as its initial state.
  • The UI on being rendered has its components access the present state of the Redux store and use it to decide what must be rendered.

Updates

  • Some action occurs (e.g., a user clicking a button)
  • An action is dispatched to the Redux store by the app code.

e.g., dispatch({type: 'counter/incremented'})

  • The store reruns the Reducer function with the previous state and the present action. The return value is then saved as the new state.
  • The store notifies all subscribed parts of the UI about updates.
  • Every user interfaces components checks if the parts of the state they need have changed.
  • Every component whose data has been changed forces a re-render with the new data to update what is shown on the screen.

Frequently Asked Questions

Q1. Is Redux front-end or back-end?

Redux can be used with user interfaces for the client-side (front-end), and since it is just JavaScript, it can also be used at the back-end on the server-side.

 

Q2. What is the flow of Redux?

The flow of data in Redux is unidirectional, meaning the application data follows a one-way binding data flow. As the application becomes complex and growing, issues and new features are harder to reproduce if the user doesn’t have control over the app state.

 

Q3. How and why is Redux immutable?

Redux doesn’t mutate the state but creates a copy of the object, where the state is updated. This means that Redux is immutable which makes it safer to handle data. Debugging requires the reducers to have no side effects so that the user can jump between different states correctly, which is provided by immutability.


Q4. What is the use of Redux in React JS?

React Redux allows data to be read by React components from a Redux store. It also let react components dispatch actions to the store to update data. Apps can be scaled because the state is sensibly managed due to unidirectional data flow.

Key takeaways

In this article, we learned about Redux fundamental concepts and data flow for both setup and updation, with the help of examples and code snippets.

You can go to CodeStudio to try and solve more problems for practice. Send this blog to your friends if you found it helpful! Until then, All the best for your future endeavours, and Keep Coding.

Was this article helpful ?
0 upvotes

Comments

No comments yet

Be the first to share what you think