Return to Redux

What?

Redux is a state container for JavaScript. It can be used with React, Vue, vanilla JS, or any other JS library/framework. It stores and manages state, reliably. It is easy and predicable to keep track of the state changes in your app using Redux.

Before the use context API came out in React, you could not pass data between two components if they were on the same level. You would have to handle the state in a parent component of both of them. For bigger applications with many nested components it became a huge pain. You would have to manage state at a very high level, and prop drill it all the way down to where you needed it.

Redux has state store that is not part of the regular chain, any component can use it. You can now do this with the context hook, but Redux was introduced before the context API came out.

Redux vs useContext

With use context, anytime you subscribe to a context in a component it will render if any value in that context changes even if that component does not use that value of the context. This works fine for smaller apps. For larger apps, it can become a problem. Any component that is wrapped in a context consumer will re-render if any value in the context provider changes.

useContext was designed to pass props down from a parent component to a deeply nested child without prop drilling all the way to the center of the earth. The bad news is that if

When to use?

So when do you decided to use Redux over the context API? There is no set rule. Larger more complicated apps with lots of data changes, Redux. Smaller apps, use context. A smaller app using Redux might be slower with it than the context API. Or you might not notice a difference with either.

Hi, I’m a Redux maintainer.

Context and Redux are very different tools that solve different problems, with some overlap.

Context is not a “state management” tool. It’s a Dependency Injection mechanism, whose only purpose is to make a single value accessible to a nested tree of React components. It’s up to you to decide what that value is, and how it’s created. Typically, that’s done using data from React component state, ie, useState and useReducer. So, you’re actually doing all the “state management” yourself - Context just gives you a way to pass it down the tree.

Redux is a library and a pattern for separating your state update logic from the rest of your app, and making it easy to trace when/where/why/how your state has changed. It also gives your whole app the ability to access any piece of state in any component.

So, yes, you can use both of them to pass data down, but they’re not the same thing.

Source

React-Redux

This is a library to bind React and Redux together.

Basics:

In Redux you have a Store, Action, and Reducer.

The Store is easy, it just keeps the state of your app. An object tree.

The Action describes what should happen to the state when something occurs. An object with a type.

The Reducer ties the store and actions together and carries out the changes. A pure function that takes in the previous state and an action.

Three Principles:

First: “The state of your whole application is stored in an object tree within a single store”

Second: “The only way to change the state is to emit an an action: An object describing what happened”. You are not allowed to update the state directly, only with an action. An action is a object that contains a type property with a description (such as a string) describing your action type

Third: “To specify how the state tree is transformed by actions, you write pure reducers”. This covers how the state transforms. A reducer is just a function that takes in the previous state, an action, and returns the new state based on both of them.

Updating:

The app can’t update the Redux state store directly. It must dispatch an action, which goes into a reducer, which updates the Redux store, which updates the app if needed because the app is subscribed to the Redux Store

Redux State

Actions:

The only way your app can interact with Redux store is through actions. Actions are plain JS objects that carry information from your app to the Redux store. They must have a “type” property that indicates the type of action that is going to be performed. The type property is usually a string constant. Besides the required type, you can have any other field you want on the action object. Actions are usually in returned by “action creators”. This is simply a function that returns the action object when called. Action creators make things easier for re-use. If you were dispatching the same action object from different places in your app, and suddenly needed to change part of it, you would have to change it in every file you used it in. Instead, an action creator can just be exported to each file and the main action can simply be updated which will update all the ones it is passed down to.

Reducers:

Actions only describe what happened, not what changes are to be made to the state. The reducer is charge of that. A reducer is a function that takes in the previous state and an action that returns the new state. Inside the reducer function when the new state is returned, it is always better to copy the existing state before modifying it. Very easily done with the spread operator.

Store:

For the entire app, you will only have one store (usually). The store holds the app state. It allows access to the state by getState(). It allows updating the state with dispatch(action). It registers listeners by subscribe(listener), which has a function that listens for change in the Redux store. You can unsubscribe as well.

You create a store using Redux and the createStore() function it has. The createStore function takes in the reducer, createStore(reducer). Redux also makes the state available from the store using the store.getState() method. To have your app listen to the store, you use the Redux store.subscribe() method. The store has a dispatch method as well, store.dispatch(). Here You usually invoke the action creator, which returns the action object. store.dispatch(buycCake()). To unsubscribe, you call the function that the subscribe function returns.

const redux = require("redux")
const createStore = redux.createStore
const BUY_CAKE = "BUY_CAKE"

//Action
function buyCake() {
  return {
    type: BUY_CAKE,
    info: "First Redux Action",
  }
}

//Reducer
const initialState = {
  numOfCakes: 10,
}

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case BUY_CAKE:
      return {
        ...state,
        numOfCakes: state.numOfCakes - 1,
      }
    default:
      return state
  }
}

const store = createStore(reducer)
console.log("init", store.getState())
const unsubscribe = store.subscribe(() =>
  console.log("Updated state", store.getState())
)
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
unsubscribe()

Combine Reducers

Let’s say you have a reducer for all your state. Ends up being a huge reducer if you have a lot to manage. Hard to read. Easier to split reducers off into different functions. You can do this with the Redux combineReducers() method. This lets your state be a different object as well, since they are combined. The difference is in how you access the global state object. Now the global state object has two objects inside it, cake and iceCream. You would access them such as: cake.numOfCakes and iceCream.numOfIceCreams

const redux = require("redux")
const createStore = redux.createStore
const combineReducers = redux.combineReducers

const BUY_CAKE = "BUY_CAKE"
const BUY_ICECREAM = "BUY_ICECREAM"

//Action
function buyCake() {
  return {
    type: BUY_CAKE,
    info: "First Redux Action",
  }
}

function buyIceCream() {
  return {
    type: BUY_ICECREAM,
    info: "First Redux Action",
  }
}


const initialCakeState = {
    numOfCakes: 10,

}
const initialIceCreams = {
    numOfIceCreams: 20,
}


const iceCreamReducer = (state = initialIceCreams, action) => {
  switch (action.type) {
   
    case BUY_ICECREAM:
      return {
        ...state,
        numOfIceCreams: state.numOfIceCreams - 1,
      }
    default:
      return state
  }
}
const cakeReducer = (state = initialCakeState, action) => {
    switch (action.type) {
      case BUY_CAKE:
        return {
          ...state,
          numOfCakes: state.numOfCakes - 1,
        }
      
      default:
        return state
    }
  }
const rootReducer = combineReducers({
    cake: cakeReducer,
    iceCream: iceCreamReducer
})
const store = createStore(rootReducer)
console.log("init", store.getState())
const unsubscribe = store.subscribe(() =>
  console.log("Updated state", store.getState())
)
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyIceCream())
store.dispatch(buyIceCream())
unsubscribe()

Middleware:

The suggested way to extend Redux. Places something between the the dispatch and reducer, in the middle as they say. Useful for things like async functions, logging. You import your middleware, call the Redux.applyMiddleware method, and in your store creation pass in the middleware function as a parameter after the rootReducer.

const redux = require("redux")
const createStore = redux.createStore
const combineReducers = redux.combineReducers
const reduxLogger = require("redux-logger")
const applyMiddleware = redux.applyMiddleware
const logger = reduxLogger.createLogger()

const BUY_CAKE = "BUY_CAKE"
const BUY_ICECREAM = "BUY_ICECREAM"

//Action
function buyCake() {
  return {
    type: BUY_CAKE,
    info: "First Redux Action",
  }
}

function buyIceCream() {
  return {
    type: BUY_ICECREAM,
    info: "First Redux Action",
  }
}

//Reducer
// const initialState = {
//   numOfCakes: 10,
//   numOfIceCreams: 20,
// }
const initialCakeState = {
  numOfCakes: 10,
}
const initialIceCreams = {
  numOfIceCreams: 20,
}
// const reducer = (state = initialState, action) => {
//     switch (action.type) {
//       case BUY_CAKE:
//         return {
//           ...state,
//           numOfCakes: state.numOfCakes - 1,
//         }
//       case BUY_ICECREAM:
//         return {
//           ...state,
//           numOfIceCreams: state.numOfIceCreams - 1,
//         }
//       default:
//         return state
//     }
//   }

const iceCreamReducer = (state = initialIceCreams, action) => {
  switch (action.type) {
    case BUY_ICECREAM:
      return {
        ...state,
        numOfIceCreams: state.numOfIceCreams - 1,
      }
    default:
      return state
  }
}
const cakeReducer = (state = initialCakeState, action) => {
  switch (action.type) {
    case BUY_CAKE:
      return {
        ...state,
        numOfCakes: state.numOfCakes - 1,
      }

    default:
      return state
  }
}
const rootReducer = combineReducers({
  cake: cakeReducer,
  iceCream: iceCreamReducer,
})
const store = createStore(rootReducer, applyMiddleware(logger))
console.log("init", store.getState())
const unsubscribe = store.subscribe(() => {})

store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyIceCream())
store.dispatch(buyIceCream())
unsubscribe()

Async:

Calling an API? Async is for you! Don’t hold up the line waiting for one silly API to slowly return something. For data fetching the state usually has three properties: loading, the data, an error message.

  • Loading is usually set to a simple boolean, true if the data is being fetched, and false if not. Often a spinner will be displayed while loading is set to true. Once the data is loaded, loading is set to false and the spinner goes away.
  • Data, usually set to an empty array
  • Error, this is the error message to display when things go sideways. This happens more than you think.

The Actions in an async call. Normally has three actions. The request name, such as FETCHCOINSALL for the type. It also has an action for a successful load case, such as FETCHCOINSSUCCESS. And of course an action type for when your request fails, FETCHCOINSFAILURE.

The reducer takes in the dispatch and sets the object state based on the type. A FETCHCOINSALL request type will set `loading: true`. A FETCHCOINSSUCCESS will set the data to the response, and loading to false. An action type of FETCHCOINSFAILURE would set loading to false and the error to the error the API returns.

Async Redux-Thunk

Awesome name! Redux-Thunk is used for async action creators, a standard often used for async calls with Redux. It’s a middleware, it allows an action creator to return a function instead of an action object. It does not have to be a pure function, and thus can make API calls and can also dispatch actions because it receives the dispatch method as it’s argument.

const redux = require("redux")
const createStore = redux.createStore
const applyMiddleware = redux.applyMiddleware
const thunkMiddleware = require("redux-thunk").default
const axios = require("axios")

const initialState = {
  loading: false,
  users: [],
  error: "",
}

const FETCH_USERS_REQUEST = "FETCH_USERS_REQUEST"
const FETCH_USERS_SUCCESS = "FETCH_USERS_SUCCESS"
const FETCH_USERS_FAILURE = "FETCH_USERS_FAILURE"

const fetchUsersRequest = () => {
  return {
    type: FETCH_USERS_REQUEST,
  }
}

const fetchUSerSuccess = (users) => {
  return {
    type: FETCH_USERS_SUCCESS,
    payload: users,
  }
}

const fetchUserFailure = (error) => {
  return {
    type: FETCH_USERS_FAILURE,
    payload: error,
  }
}

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_USERS_REQUEST:
      return {
        ...state,
        loading: true,
      }
    case FETCH_USERS_SUCCESS:
      return {
        ...state,
        loading: false,
        users: action.payload,
        error: "",
      }
    case FETCH_USERS_FAILURE:
      return {
        ...state,
        loading: false,
        users: [],
        error: action.payload,
      }
  }
}

//action creator
const fetchUsers = () => {
  //dispatch the fetch_user_ request before making the call so that the loading is set to true

  return function (dispatch) {
    dispatch(fetchUsersRequest())
    axios
      .get("https://jsonplaceholder.typicode.com/users")
      .then((res) => {
        const users = res.data.map((users) => users.id)
        dispatch(fetchUSerSuccess(users))
      })
      .catch((error) => {
        dispatch(fetchUserFailure(error.message))
      })
  }
}

const store = createStore(reducer, applyMiddleware(thunkMiddleware))
store.subscribe(() => {
  console.log(store.getState())
})
store.dispatch(fetchUsers())

Async Thunk

The key takeaway is that thunk allows an action creator to return a function instead of an object. This allows us to make API calls and dispatch other settings.

Folder Structure in React with Redux

There is no standard, it’s a massive free for all. Might as well come up with your own, or join in the fray and pick the best one. Make sure to tell anyone else who picks a different file structure how wrong they are.

React Redux with Hooks

Since React Redux v7.1 there are now hooks that allow you to subscribe to the store and dispatch without using connect() to wrap your components. useSelector() is the hook. Imported from react-redux, this hook accepts a parameter, the selector function. The selector function receives the Redux state as it’s argument and can return state values from it. Below is the connect() vs the useSelector() version of the same component/

The selector is approximately equivalent to the mapStateToProps argument to connect conceptually.

There is also the useDispatch() hook that can be used in place of mapStateToProps and connect.

This hook returns a reference to the dispatch function from the Redux store. You may use it to dispatch actions as needed.

Hooks do seem better than connect() in terms on how simple it is. Apparently connect() can, however, be better in performance in some cases according to the docs on it. And this post: connect() has always automatically kept your own component from re-rendering if the parent renders, but the props being passed down haven’t changed.

With hooks, you would need to do that yourself, which can be quickly done by wrapping your component in React.memo(). (In fact, now that connectis implemented using hooks internally, that’s exactly how connect itself does it.)

At that point, it’s back to standard React performance optimization approaches. Write your app as normal, profile in production if it seems slow, find components that are rendering more than they “should”, and wrap them in React.memo() to improve behavior if it’s really needed.

In other words, don’t worry about the perf aspects for now.

Source

// connect()

import React from "react"
import { connect } from "react-redux"
import { buyCake } from "../redux/index.js"

function CakeContainers(props) {
  return (
    <div>
      <h2>Number of cakes - {props.numOfCakes}</h2>
      <button onClick={props.buyCake}>Buy Cake</button>
    </div>
  )
}

const mapStateToProps = (state) => {
  return {
    numOfCakes: state.numOfCakes,
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    buyCake: () => dispatch(buyCake()),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(CakeContainers)
//hooks

import React from "react"
import { useSelector, useDispatch } from "react-redux"
import { buyCake } from "../redux/index.js"
function HooksCakeContainer() {
  const numOfCakes = useSelector((state) => state.numOfCakes)
  const dispatch = useDispatch()
  return (
    <div>
      <h2>Num of Cakes - {numOfCakes}</h2>
      <button onClick={() => dispatch(buyCake())}> Buy Cake</button>
    </div>
  )
}

export default HooksCakeContainer

React Redux Middleware

Bascially the same as regular Redux, here is an example with dev tools added to log it out on the console:

import { createStore, applyMiddleware } from "redux"
import rootReducer from "./rootReducer"
import { logger } from "redux-logger"
import { composeWithDevTools } from "redux-devtools-extension"
const store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(logger))
)
export default store

Redux Dev Tools

Sweet! This is super awesome, amazing charts. First you add the extension to your browser. Next you install the dev tools package via NPM.

Redux vs useContext

Why? The new context hook is awesome, so why do we need Redux still? After reading a ton of posts on Reddit by some of the project maintainers, Redux is still useful for larger projects. I switched to the context hook in 2019 and have barely touched Redux since. I used it in existing projects, but never really went back to using it for my own projects. I never had a reason to… until:

Data. Data everywhere!

I started working on a large personal project, tons of API calls, large amounts of data, tons of components. I made it pretty far with the context hook successfully. Eventually my context provider I was wrapping the app with became rather large. So I created a second context provider, and wrapped the app with both of them. And a 3rd followed shortly. A fast stackoverflow search informed me that there was a bit of a riff on this topic. The problem I was facing is that changes to any context value caused re-renders of everything that the context enclosed. Even if a component does not use the new value from the context, it will still render again.

Performance

Obviously re-renders when not needed are not ideal. For smaller apps, it works great. As the projects grows you might take a performance hit. Some people claim that you should not even worry about it until you can actually see a difference in the app speeds. Others disagree rather strongly. In my case, I was pulling a ton of historical cryptocurrency data in via API calls and displaying it using Chart.js. I had a dashboard with a bunch of widgets that also displayed data. If I changed the chart to display a different time frame, the dashboard would also re-render because both were using data from the context provider even when the actual values the dashboard displayed would not change. There are a few ways around this, but in the end I just decided it was best to go crawling back to Redux.

Redux