React-Redux, the basic basic.

React-Redux, the basic basic.

ยท

14 min read

While learning React, you probably have heard people talk about redux and you got curious to find out what the fuss is about. Chances are you've gone through tons of tutorials and articles but you only end up copy-pasting lines of code without understanding what is going on.

Note: As at the time of writing this article, I am a learner and I am open to correction(s) on misrepresentation(s) of concepts discussed in this article or any future article. Thank you for understanding.

Table of content

Introduction

What Is Redux?

That is the first major question that needs an answer. Who is in a better position to answer other than Redux. As stated in their website, Redux is "A Predictable State Container for JS Apps". To put it in another way, It is a state management service that works for any JavaScript application and it maintains consistency in its state management patterns, thus predictable.

Why Redux In React?

In a typical React application, data is passed top-down (parent to child) via props. For simple applications, this may not be a major issue but when it comes to a fairly large application, passing state through numerous components will cause unnecessary re-rendering of components which affects performance.

There are already several ways to maintain and pass down states in React without passing through several components. So why bother with Redux? As a beginner, a reason I can think of is that, Redux is not tied to React so its strength can be utilized in other JavaScript libraries and frameworks.

With this introduction, let's get started.

React App Setup

Let's setup a react app where we will be integrating redux. I will name the folder reactredux. Change directory to a suitable folder and run the code below in the node terminal

npx create-react-app reactredux

If it did not throw an error, a folder named reactredux ( or whatever you chose to name yours) should be present. In the terminal run the code below to change directory into the reactredux folder.

cd reactredux

Now that you are in the folder run the code below to add redux as a dependency

npm install redux react-redux --save

If you have VsCode installed run the code below, otherwise open the folder in a code editor of your choice.

code .

After the command runs, hurray ๐ŸŽ‰you are now set to use Redux.

Redux Proper

Redux Basic Structure

Redux comprises of three main aspects;

  1. Actions
  2. Reducers
  3. Store

Actions

Actions are objects that describes what action you want perform on the state. A basic action object looks like this.

const action ={
         type:"ACTION_DESCRIPTION",
         payload: data
     }

To make an action reusable, we will have to convert it into a function pseudo-code.

const actionDescription = (data)=>{return {
         type:"ACTION_DESCRIPTION",
         payload: data
     }}

With this, anytime actionDescription(data) is called, it returns an object.

Reducers

Reducers are functions that accept the current state of the application and an action object as arguments to return a new state. Let's translate that into JavaScript pseudo-code.

const reducer =(state, action)=> newState;

Since a reducer can receive different actions, it makes sense for the reducer to act differently based on the given action.

const initialState = {
defaultState
}
// use initialstate if no previous state is found
export default (state = initialState, action) => {
    switch (action.type) {

    case typeName:
        return newState;
    case anotherTypeName:
        return aNewState;

    default:
        return state
    }
}

Note: You are not directly passing state and action to the reducer, the store is in charge of that.

Store

The store is the ultimate source of truth, no one sets, or gets a state from Redux except through it. (I'm bad with references). What the above gibberish means is that the store handles every transaction between the app, actions, and reducer. Whatever component that needs the state asks the store. Whatever component that needs to change the state sends an action to the store. That is where Redux finally comes into the picture. Let's see Redux create store.

import {createStore} from 'redux'
const store = createStore(reducer)
// Create store from reducer

We have just created the store, now let's explore some of the methods the store offers

getState()

It is exactly as it sounds, it is used to get the state.

store.getState()
// returns current state
subscribe(()=>{})

This where you pass in a function you want to run anytime the state changes.

store.subscribe(()=> console.log(store.getState()))
// Run console.log(store.getState()) every time the state changes.

When a subscribe function execute, it returns an unsubscribe function which can be accessed by assigning the returned value to a const.

const unsubscribe = store.subscribe(()=> console.log(store.getState()))
// Unsubscribe by calling unsubscribe ()
dispatch(actionDescription())

This is used to call the reducer function and passing the action and current state to it.

store.dispatch(actionDescription(data))

Before we start coding

Permit me to add one analogy before we move coding proper. Think of a shop with a strict receptionist who only accept invoice for anything you want to buy and would not permit you to buy anything beyond what the invoice states, the receptionist then sends the invoice to the counter for the product, take the stock after your transaction is completed, then give you what you ordered.

Apply it here, you are the component trying to complete a transaction, the invoice is equivalent to the action, the strict receptionist is the redux store, and the person at the counter is the reducer.

Okay, now let's move to see how everything interact on code.

Coding Proper

Okay well, let's code!

Direct yourself to the src folder. Now let's create the component we would work on in the src folder. Let's name it Shop.js. Let's add a functional component.

import React from 'react';

function Shop() {
    return (
        <div>

        </div>
    )
}

export default Shop

open the App.js file, let's clear the part we don't need and import Shop.js.

import React from 'react';
import './App.css';
import Shop from './Shop';

function App() {
  return (
    <div className="App">
     <Shop/>
    </div>
  );
}

export default App;

Let's move back to the Shop component , and add relevant tags.

import React,{useState} from 'react'

function Shop() {
    const [number, setNumber] = useState('')
    return (
        <div>
            <h4>Total Shop item</h4>
            <input type='number' onChange={ e => setNumber( e.target.value )} />
            <button>Buy {number} item(s)</button>
        </div>
    )
}

export default Shop

In the command terminal run

npm run start

If you used my exact code, you should get ๐Ÿ‘‡

Time for Redux ๐Ÿ”Š

Create a Redux.js file in the src folder. Let's add an action.

const buyItem = (payload) => ({
    type: 'BUY_ITEM',
    payload
})

Let's add the reducer function

const initialState = {
    shopItem: 50
}
const reducer = (state = initialState, action) => {
    switch (action.type) {

    case 'BUY_ITEM':
        return { ...state, shopItem: state.shopItem - action.payload }

    default:
        return state
    }
}

Since we know we are going to get 'type' and 'payload' from action, let's refactor the code using object destructuring.

// if no payload is added when buyItem is called use 1 as default
const buyItem = (payload=1) => ({
    type: 'BUY_ITEM',
    payload
})

const initialState = {
    shopItem: 50
}
const reducer = (state = initialState, { type, payload }) => {
    switch (type) {

    case 'BUY_ITEM':
        return { ...state, shopItem: state.shopItem - payload }

    default:
        return state
    }
}

Now let's setup redux store. First import createStore from redux.

import {createStore} from 'redux'

Now create store from the reducer.

const store = createStore(reducer)

Export action and store for react app to utilize.

import { createStore } from 'redux'

export const buyItem = (payload=1) => ({
    type: 'BUY_ITEM',
    payload
})

const initialState = {
    shopItem: 50
}
const reducer= (state = initialState, { type, payload }) => {
    switch (type) {

    case 'BUY_ITEM':
        return { ...state, shopItem: state.shopItem - payload }

    default:
        return state
    }
}

export const store = createStore(reducer)

Now let's move back to App.js and link redux to the JavaScript app. To link store to the App, we use the Provider component from the react-redux package.

First, import Provider from react-redux and make sure you import the store too

import { Provider } from 'react-redux';
import { store } from './Redux';

Second, wrap everything in the 'return' in the Provider component and pass store as a prop. Your App.js should look like this now.

import React from 'react';
import './App.css';
import Shop from './Shop';
import { Provider } from 'react-redux';
import { store } from './Redux';

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <Shop/>
      </div>
    </Provider>
  );
}

export default App;

Now that we are done providing the store at the top-most level, Let us move to the Shop.js to access the state provided by the store.

If you are conversant with React, you will notice that we did not pass store as prop to Shop before leaving App.js. Will we be importing store and Provider in Shop too? The answer is no. This time, we will 'connect' to the provided state at the top most level. Let's see how to connect.

There are two main methods of connecting; using HOC or using hooks. I will be using higher order component method.

To connect, import 'connect' from react-redux.

import {connect} from react-redux

Things get weird here for most people so let me talk a little about the connect function before we proceed. We won't be adding till the connect is explained.

Connect

Connect, as the name implies is used to connect a child component to the store provided at the top-most component.

How does it work?

Connect typically requires three arguments to function. check the pseudo-code below.

connect(state=>takeRequiredStateAndAddToProp,dispatch=> giveComponentPermissionToDispatchAction)(componentToConnectTo)

As I tried to show from the pseudo-code above ๐ŸŒš, the connect is expecting a function in the first two arguments, and expecting a component in the third.

In the first argument, it expects a function that accepts state as an argument and return the part of state it needs so it can connect that part of the state to props. (This mimics store.getState(), then pass the state gotten as an argument). Conventionally, the expected function is named 'mapStateToProps'.

You can name the function anything but for consistency name it 'mapStateToProps'.

A typical mapStateToProps functions looks like this pseudo-code.

const mapStateToProps = (state) => ({
    aNeededstate: state.aNeededstate,
    anotherState: state.anotherState
})

In the second argument, it expects a function that accepts dispatch as an argument and return an object of functions that can dispatch actions it needs so it can connect the object to props. (This mimics dispatch = state.dispatch() and then pass dispatch as an argument). Conventionally, the expected function is named 'mapDispatchToProps'.

You can name the function anything but for consistency name it 'mapDispatchToProps'.

A typical mapDispatchToProps functions looks like this pseudo-code.

// import necessary actions because you will dispatch it here
const mapDispatchToProps = (dispatch) => ({
    actionOne: () => dispatch(actionOne),
    actionTwo: () => dispatch(actionTwo)
})

Connect will now look very close to like this.

connect(mapStateToProps,mapDispatchToProps)(component)

If you only want to dispatch action but you don't want the component to access state or it does not need the redux state, you don't need to declare mapStateToProps, you can just add null in its place.

connect( null, mapDispatchToProps )(component)

Also, if you only need to access state, you can pass only mapStateToProps.

connect(mapStateToProps)(component)

Now, let's shift our focus back to Shop.js.

Import the action(s) needed. In this case, buyItem.

import { buyItem } from './Redux'

After the Shop function, let's declare the mapStateToProps and mapDispatchToProps functions.

// since the shopItem is the only content of the state, let's spread the state
const mapStateToProps = (state) => ({
    ...state
})
// You can also write
// const mapStateToProps = state =>state
// or
// const mapStateToProps = (state) => ({
//    shopItem: state.shopItem
// })
const mapDispatchToProps = dispatch => ({
    buyItem: (number)=>dispatch(buyItem(number))
})

Let's connect the component to the store. Instead of exporting the component directly we will wrap the connect around it before exporting.

export default connect( mapStateToProps, mapDispatchToProps )(Shop)

The code should look like this.

import React,{useState} from 'react'
import { connect } from 'react-redux'
import { buyItem } from './Redux'

function Shop() {
    const [number, setNumber] = useState('')
    return (
        <div>
            <h4>Total Shop item</h4>
            <input type='number' onChange={e=>setNumber(e.target.value)} />
            <button>Buy {number} item(s)</button>
        </div>
    )
}

const mapStateToProps = state => state

const mapDispatchToProps = dispatch => ({
    buyItem: (number)=>dispatch(buyItem(number))
})


export default connect( mapStateToProps ,mapDispatchToProps )(Shop)

With this, redux state and dispatch have been "magically" added to props. (maybe 'map' at the start of the function name means "Magically Add to Props" #JustBants ๐Ÿ˜†.

To access state, just use {props.stateContent}. We want to access shopItem in the state and display in the h4 tag

// Add props as an argument in the Shop function
<h4>Total Shop item: {props.shopItem} </h4>

To access dispatch, just use {props.dispatchName}. We want to access buyItem in the dispatch and use in the button tag

<input type='number' onChange={e=>setNumber(e.target.value)} value={number}/>
            <button onClick={()=>{props.buyItem(number); setNumber('')}}>Buy {number} item(s)</button>

The code now look like this.

import React,{useState} from 'react'
import { connect } from 'react-redux'
import { buyItem } from './Redux'

function Shop(props) {
    const [number, setNumber] = useState('')
    return (
        <div>
            <h4>Total Shop item: {props.shopItem}</h4>
            <input type='number' onChange={e=>setNumber(e.target.value)} value={number}/>
            <button onClick={()=>{props.buyItem(number); setNumber('')}}>Buy {number} item(s)</button>
        </div>
    )
}

const mapStateToProps = state => state

const mapDispatchToProps = dispatch => ({
    buyItem: (number)=>dispatch(buyItem(number))
})

export default connect( mapStateToProps ,mapDispatchToProps )(Shop)

Summary

Redux is a state container that can be used to handle states in JavaScript application.

Redux is used in React as one of the solutions to unnecessary state passing from parents to components.

Redux does not allow the React app to directly change the state, it has to dispatch an action to the reducer through store.

To bridge React and Redux, react-redux package was introduced. We used two of what react-redux provide in this article (Provider and connect).

The Provider makes the Redux store available for the React app.

The connect grants access to the state and make dispatching action possible.

That's all for now.