React Hooks in one go! (Part 2)

Prerequisites

This blog will be the continuation of React Hooks in one go!. So if you want to learn about Hooks from scratch my suggestion would be to first read that blog and if you are here to learn about useReducer and useContext only then we are good to go!!

Goal of this blog

To complete the previous blog from where we have left and mastering react hooks. It will be super fun and informative as we will go through the definitions and examples as well so let's start

useReducer()

Just like useState, useReducer is also used to store and update the state. useReducer is more preferable when you have complex state logic that involves multiple sub-values or depends on the previous state. useReducer take two things a reducer function and an initalvalue and returns the current state and the dispatch method.

Let's first quickly revise reducer function in JavaScript

Reduce()

Reducer method executes a user-supplied reducer callback function on each element of an array and returns a single accumulated value based on the reducer callback function. The reduce method takes two things the reducer function and the initial value of the accumulator. The callback reducer function also takes two arguments accumulator and current. By default, the accumulator is initialized by the first element of an array if we don't provide it.

const arr1 = [1,2,3,4,5,6]
const oddSum = arr1.reduce((acc,curr) => 
            curr%2 !== 0 ? acc+=curr : acc,0) //11
const evenSum = arr1.reduce((acc,curr) =>
            curr%2===0?acc+=curr : acc,0) //12

Here we are calculating the odd and even sum of numbers in an array. Now going back to our topic useReducer. It will also take a function as a reducer and initialState.

 const [initState,dispatch] = useReducer(reducerFunc,0)

So it is taking a reducer method and initial state in this case 0 and returns two things the state and a dispatch method to update the state. Let's now create some buttons one will increase the number and other will decrease the number by one and the third one will reset it to zero again

 <button onClick={()=>dispatch({type:"INCREEMENT"})}>+</button>
 <button onClick={()=>dispatch({type:"DECREEMENT"})}>-</button>
 <button onClick={()=>dispatch({type:"RESET"})}>Reset</button>

and now let's write the reducer function for that. We will be using switch statement for it

const reducerFunc = (state,action) => {
      switch(action.type){
        case "INCREEMENT" : 
        return state+=1
        case "DECREEMENT" : 
        return state-=1
        case "RESET":
          return state=0
        default :
        return state
      }
  }

and also we are dispatching the type when the button is clicked that we can access in action so based on the type it will update the state. Cool !!! so now lets put the initState on the view so updation can be visible

<h1>{initState}</h1>

In this way we can increase decrease reset the number using single reducer itself and if needed we can expand it as well by adding more dispatch having different actions based on different types.

Live Sandbox link

This was the case of numbers but what if we want to do the same thing on an array of objects say filtering an array of products for an e-commerce app, or sorting the trending posts in a social media app based on some condition like all items price <=1000 or most liked post first. We can easily do that with the help of useReducer.

useContext()

What if we want to manage our state globally? So the answer is to go with useContext.

What is the need?

To avoid prop drilling. Let's see what it is? Suppose a component has some state and it has some nested component with it and we want that state in the nested component as well so we need to pass those states as a prop to the child or nested component and from that to next nested component and so on until the last one. there can be possibility that some component do not need those state but still we are passing as a prop because the next one needs it. So we are unnecessary doing the repetition and also increasing the number of line of codes making our program messy and complex

const Component1 = ({user}) => {
  return (
    <>
    <h2>{user}</h2>
    <Component2 user={user} />
    </>
  )
}

const Component2 = ({user}) =>{
  return (
    <>
    {user}
    </>
  )
}

export default function App() {
  const [user,setUser] = useState("John Doe")
  return (
    <div className="App">
     <h1>{`hello ${user}`}</h1>
     <Component1 user={user} />
    </div>
  );
}

So for accessing the user inside component2 we have to pass the user prop first in component1 and then in component2. Imagine if we have 5-6 components in between component1 and component2 ? Yes, you are right we have to pass users from every component.

What's the solution ??

Context API and useContext

Context API provides a way to pass data through a component tree from parent to child component without passing props manually at each level. For this context API provide provider and consumer to create and utilize the global state.

Let's see them one by one

We will first create a Provider component and then wrap the app component inside the provider so that whatever (State) is in the Provider component will be accessible to the whole React App.

const {createContext} from "react";

const DataContext = createContext({defaultCount:0})

const DataProvider = ({children}) =>{
const [count,setCount] = useState(0)
return (
          <DataContext.Provider value={{count}}>
            {children}
          </DataContext.Provider>
    )
   }

export {DataProvider}

In Index.js File

import DataProvider from "../context/dataContext.js"
...
  <DataProvider>
        <App />
  </DataProvider>

So here we are using createContext, this is provided by react for creating context. Using this we are creating DataContext and providing it default value called defaultCountas zero. Now we are creating a custom provider DataProvider DataContext has a provider object that will help to provide state to its children here the app. Now the provider is ready, with the global object to be used.

Now let's create the consumer and see how we can utilize it in different components. For that we will be using useContext.

const {createContext,useContext} from "react";

const DataContext = createContext({defaultCount:0})

const DataProvider = ({children}) =>{
const [count,setCount] = useState(0)
return (
          <DataContext.Provider value={{count,setCount}}>
            {children}
          </DataContext.Provider>
    )
   }

const useData = () => useContext(DataContext)  // takes context as an argument

export {DataProvider,useData}

here to access the context inside any component now we will use this custom useData hook.

import {useData} from "../context/dataContext.js";

const Counter = () =>{
const {count,setCount} = useData();
return (
        <>
          <h1>{count}</h1>
           <button onClick={setCount(prev => prev+1)}>Increase Count</button>
        </>
      )
    }

now we have the access to both count and setCount here in the Counter component.

The next two hooks we are going to know are mostly for performance optimization for our apps.

useMemo() Hook

If we have some expensive, resource-consuming function that runs on every render then instead of running it again and again we can memorize the value basically cache the value. So that the function only runs if it is provided with some new values to be calculated on, else returns the cached one. This will help to improve performance.

Example -

import * as React from 'react';
import { useState, useMemo } from 'react';
import './style.css';

export default function App() {
  const [todo, setTodo] = useState([]);
  const expensiveCalculation = (num) => {
    console.log('Calculating...');
    for (let i = 0; i < 1000000000; i++) {
      num += 1;
    }
    return num;
  };
  const calculation = useMemo(() => expensiveCalculation(5), []);
  // const calc = () => expensiveCalculation(6);
  return (
    <div className="App">
      <h1>Todo</h1>
      {todo.map((el, i) => (
        <h2 key={i}>{el}</h2>
      ))}
      <button
        onClick={() => {
          setTodo((prev) => [...prev, 'abc']);
        }}
      >
        Add
      </button>
      {calculation}
    </div>
  );
}

Here we have a function called expensiveCalculation This function will be quite slow because of the heavy loop that will execute every time the component gets rendered. This can slow down our app, so to the solution of that we are using the useMemo hook, it will basically first calculate the value, and then if we call again that function with the same argument or say if rerender happen it will instead of calculating, it will pick the value from the cache making the function faster. The second argument that we passed to useMemo hook is the dependency array [] , it will ensure that when exactly should useMemo hook should run, in our case, it will run on the first render, now say if our function depends on the number we are passing while calling it then we will pass the number in dependency array so that if number changes calculate the value and so on.

Now you must think wow! this is a great idea to speed up the functions so why should we not memoize all the functions in the app to make them faster🤔

We must not memoize them because it gives you some performance overhead and some memory overhead. For example, if we use useMemo in every function then it will need some extra memory to store cache (previous value) and also cause the performance issue as you are calling an additional function. So it should be only used when you actually need the performance benefits when the function you use is incredibly slow then using memo is great !!!!

Hope you enjoyed reading about advance hooks. Thanks for staying up to here with me. See you in the next blog 😇😇. Until that

byee