#

Tue Jan 02 - Written by: Ritesh Kumar

All React Hooks and Concepts In A Single Post!! ๐Ÿค—

Unleash your inner fashionista as we explore Tailwind CSS โ€“ the runway-ready framework that believes every website deserves to strut its stuff. Glam up your astro.js projects with style!

First of all React is a JS Library not a Complete Framework ๐Ÿ™‚ so for making a complete web application you need to know a lot of other things ๐Ÿคญ that you can use with React. In this Post I will cover React concepts, Hooks and some good practices Ofcโ€ฆ ๐Ÿ˜ We use React to make reusable components that can be used in a logical way to make UI. Making components in React is as easy as making a function ๐Ÿคฉ. For Example ๐Ÿ‘‡๐Ÿป it is a simple react component in which we can pass data as arguments which can be easily referenced inside the functions

function Component(props){
    return <h1>{props.text}</h1>
}

#Ohk But Now what are States in React?? The state object is where you store property values that belongs to the component. When the state object changes, the component re-renders which basically allows us to manage changing data in an application ๐Ÿ†’. Now Lets Learn about states using useState()


useState()

const component = () => {
    // Tip: use states only inside components
    // lets console.log the state and lets see what it returns
    console.log(useState(100));
    // this will return an array [100,f]
    // basically this returns a state and a function to update the state
    // we can destructure the array and get the state and the function
    const [state, setState] = useState(100);


    return (
        <div>
            hiiieeee
        </div>
    )
}

but you canโ€™t โ˜ ๏ธ directly update the value of state by using = operator as this will change the value but it will not re render the component so basically react wants ๐Ÿ˜ฉ that you pass the value in the setState function if you need to change the state.

passing functions inside the useState() ๐Ÿค”

// you can also pass function in useState it will set the initial value to what function returns it is useful when you use computationaly high task as initial state
const [state, setState] = useState(() => {
        console.log("initial state");
        return 100;
});

passing functions inside the setState() ๐Ÿ™ƒ

onClick={() => {
      // here value in the props is the state
      setState((value) => {
      //you can use this method when you need to update the state and you need the previous value of state
           return value + 1;
      });
}} 

useEffect()

Use effect hook has 2 parts first one is a function and second one is dependency array which is optional

useEffect(()=>{},[])
// we will get a console log every time any state changes.

// for example if you have 2 states in your component and any of 
// them changes then we will get the console log

// this is something we mostly dont want.
useEffect(() => {
   console.log('change');
})

Your first useEffect() call will always run when your component gets mounted first time in the DOM.

Dependency array ๐Ÿค 

We can specify the states inside the dependency array of useEffect() so that it will only monitor the changes of those states which are mentioned in the dependency array๐Ÿ˜ฎโ€๐Ÿ’จ.

    const [state1, setState1] = useState(0);
    const [state2, setState2] = useState(0);
    useEffect(() => {
        console.log('state1 changed');
    }, [state1])

Remember: Do not update the state in which you used the useEffect() without a proper logic it will create an infinite loop ๐Ÿฅต

Cleanup function

useEffect always returns a cleanup function which you can use to remove unwanted behaviours thec leanup function does not only run when our component wants to unmount, it also runs right before the execution of the next scheduled effect read in detail

useEffect(() => {
        console.log(`state1 changed | ${state1}`);
        return () => {
            console.log('state1 unmounted | ', state1);
        }
    }, [state1])

Image description

you can fetch data from an api like this ๐Ÿ‘‡๐Ÿป

useEffect(() => {
        const url = "https://jsonplaceholder.typicode.com/todos/1";
        const fetchData = () => {
            fetch(url)
                .then(res => res.json())
                .then(data => {
                    setState(data.title)
                })
        }
        fetchData();
    }, []);

useContext()

Image description

The Context API provides data even to the deepest level of component in the react component tree without passing it in props

import { createContext } from "react";
import { useState } from "react";

const StoreContext = createContext();

const component = () => {
    const data = useState({
        name: 'Ritesh',
        email: 'nyctonio.dev@gmail.com',
    })[0];

    const Child = () => {
        return <div>
            <StoreContext.Consumer>
                {value => <h1>name is {value.name}</h1>}
            </StoreContext.Consumer>
        </div>
    }

    return (
        <StoreContext.Provider value={data}>
            <Child />
        </StoreContext.Provider>
    )
}

export default component;

You can wrap your top level component in Your Context Provider and use it inside a function by Context Consumer.What useContext do is it replaces Context Consumer and we can get data by using useContext directly.

See this example ๐Ÿ‘‡๐Ÿป.

import { createContext, useContext } from "react";
import { useState } from "react";

const StoreContext = createContext();

const component = () => {
    const data = useState({
        name: 'Ritesh',
        email: 'nyctonio.dev@gmail.com',
    })[0];

    const Child = () => {
        const value = useContext(StoreContext);
        return <div>
            <h1>name is {value.name}</h1>
        </div>
    }

    return (
        <StoreContext.Provider value={data}>
            <Child />
        </StoreContext.Provider>
    )
}

export default component;

read more


useReducer()

useReducer is used for state management in React it is somewhat similar to reducer function in javascript.

// useReducer function accepts 2 params reducer function and initialState

useReducer(reducer,initialState)

// reducer functions accepts 2 params currentState and action for that and returns a new State

reducer(currentState,action)

lets create a simple counter using useReducer

import { useReducer } from 'react'

const initialState = 0;
const reducer = (state, action) => {
    switch (action) {
        case 'increment':
            return state + 1;
        case 'decrement':
            return state - 1;
        default:
            return state;
    }
}

export default function main() {
    const [count, dispatch] = useReducer(reducer, initialState);

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => dispatch('increment')}>+</button>
            <button onClick={() => dispatch('decrement')}>-</button>
        </div>
    )
}

we can make it more complex by making our state an object

import { useReducer } from 'react'

const initialState = {
    firstCounter: 0,
    secondCounter: 0
};
const reducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return { ...state, firstCounter: state.firstCounter + action.value };
        case 'decrement':
            return { ...state, firstCounter: state.firstCounter - action.value };
        default:
            return { ...state };
    }
}

export default function main() {
    const [count, dispatch] = useReducer(reducer, initialState);

    return (
        <div>
            <p>Count: {count.firstCounter}</p>
            <button className='bg-gray-200 p-2' onClick={() => dispatch({ type: 'increment', value: 2 })}>
                increase by 2
            </button>
            <button className='bg-gray-200 p-2' onClick={() => dispatch({ type: 'decrement', value: 4 })}>
                decrease by 4
            </button>
        </div>
    )
}

Or We can use multiple useReducers ๐Ÿ‘‡๐Ÿป

import { useReducer } from 'react'

const initialState = 0;
const reducer = (state, action) => {
    switch (action) {
        case 'increment':
            return state + 1;
        case 'decrement':
            return state - 1;
        default:
            return state;
    }
}

export default function main() {
    const [count, dispatch] = useReducer(reducer, initialState);
    const [count2, dispatch2] = useReducer(reducer, initialState);

    return (
        <div>
            <p>Count: {count}</p>
            <button className="bg-gray-100 p-2 m-2"
                onClick={() => dispatch('decrement')}>-</button>
            <button className="bg-gray-100 p-2 m-2"
                onClick={() => dispatch('increment')}>+</button>

            <p>Count2: {count2}</p>
            <button className="bg-gray-100 p-2 m-2"
                onClick={() => dispatch2('increment')}>+</button>
            <button className="bg-gray-100 p-2 m-2"
                onClick={() => dispatch2('decrement')}>-</button>
        </div>
    )
}

When to use useState and when useReducer ????

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks ๐Ÿ˜Ž.


useReducer() with useContext()

With the use of useContext and useReducer we can manage global states at any level of component tree try out this example ๐Ÿ‘‡๐Ÿป


// main.jsx
import React from 'react'
import { useReducer } from 'react'
import ChildrenA from '../components/ChildrenA';

export const StateContext = React.createContext();
const initialState = 0;
const reducer = (state, action) => {
    switch (action) {
        case 'increment':
            return state + 1;
        case 'decrement':
            return state - 1;
        default:
            return state;
    }
}

export default function main() {
    const [count, dispatch] = useReducer(reducer, initialState);
    return (
        <div>
            <StateContext.Provider
                value={{ countState: count, countDispatch: dispatch }}>
                <ChildrenA />
            </StateContext.Provider>
        </div >
    )
}

// ChildrenA.jsx

import React from 'react'
import ChildrenB from './ChildrenB'
import { StateContext } from '../pages/main'
import { useContext } from 'react'

export default function ChildrenA() {
    const { countState, countDispatch } = useContext(StateContext)
    return (
        <div>
            In child A count state is {countState}
            <ChildrenB />
        </div>
    )
}

// ChildrenB.jsx

import React from 'react'
import { StateContext } from '../pages/main'
import { useContext } from 'react'

export default function ChildrenB() {
    const { countState, countDispatch } = useContext(StateContext)
    return (
        <div>
            <p>Count is {countState}</p>
            <button onClick={() => countDispatch('increment')}>+</button>
            <button onClick={() => countDispatch('decrement')}>-</button>
        </div>
    )
}

your both state will change simultaneously

Image description


useCallback()

lets see this code and try to understand the behaviour of functions in React

import React from 'react'

export default function main() {

    function Sum() {
        return (a, b) => a + b;
    }
    const func1 = Sum();
    const func2 = Sum();
    console.log(func1 === func2);

    return (
        <div>main</div>
    )
}

If you will run this code you will get false in console.log

Image description

Now with an example lets try to understand how we can use useCallback

// main.jsx
import React, { useState } from 'react'
import ChildrenA from '../components/ChildrenA';
import ChildrenB from '../components/ChildrenB';
import ChildrenC from '../components/ChildrenC';

const main = () => {
    const [state1, setState1] = useState(0);
    const [state2, setState2] = useState(0);

    const handleClickA = () => {
        setState1(state1 + 1);
    }

    const handleClickB = () => {
        setState2(state2 + 1);
    }

    return (
        <div className='flex flex-col justify-center items-center'>
            <ChildrenA value={state1} handleClick={handleClickA} />
            <ChildrenB value={state2} handleClick={handleClickB} />
            <ChildrenC />
        </div>
    )
}

// what react memo do is it re-render the component only when the props change
export default React.memo(main);

// ChildrenA.jsx
import React from 'react'

function ChildrenA({ value, handleClick }) {
    console.log('ChildrenA');
    return (
        <div>ChildrenA  {value}
            <button className='bg-gray-200 p-2 m-2' onClick={handleClick} >Click</button>
        </div>

    )
}

export default React.memo(ChildrenA);

// ChildrenB.jsx
import React from 'react'

function ChildrenB({ value, handleClick }) {
    console.log('ChildrenB');
    return (
        <div>ChildrenB {value}
            <button className='bg-gray-200 p-2 m-2' onClick={handleClick} >Click</button>
        </div>
    )
}

export default React.memo(ChildrenB);

// ChildrenC.jsx

import React from 'react'

function ChildrenC() {
    console.log('ChildrenC');
    return (
        <div>ChildrenC</div>
    )
}

export default React.memo(ChildrenC);

when you see console.log in your browser initially all three components renders but on clicking any click button only 2 components rerenders Note: here we used React.memo() thats why ChildrenC do not got rerendered because the props do not changed but why on changing ChildrenA ChildrenB also gets re render

The reason is on rerender of main function the handleClick function do not remains the same as previous one I explained this above in the blog thats why React notices change in props so it rerenders both ChildrenA and ChildrenB.

Image description

To Solve this we will use useCallback

useCallback returns a memoized callback.

useCallback accepts a function and a dependency array same as useEffect

now lets change our code in main function and see the logs

// main.jsx

import React, { useState, useCallback } from 'react'
import ChildrenA from '../components/ChildrenA';
import ChildrenB from '../components/ChildrenB';
import ChildrenC from '../components/ChildrenC';

const main = () => {
    const [state1, setState1] = useState(0);
    const [state2, setState2] = useState(0);


    const handleClickA = useCallback(() => {
        setState1(state1 + 1);
    }, [state1])

    const handleClickB = useCallback(() => {
        setState2(state2 + 1);
    }, [state2])

    return (
        <div className='flex flex-col justify-center items-center'>
            <ChildrenA value={state1} handleClick={handleClickA} />
            <ChildrenB value={state2} handleClick={handleClickB} />
            <ChildrenC />
        </div>
    )
}

// what react memo do is it re-render the component only when the props change
export default React.memo(main);

now you can see everything is fine ๐Ÿ‘‡๐Ÿป.

Image description


useMemo()

useCallback returns a memorized function similarly useMemo returns a memorized value for example we need to find the factorial and only recalculate when number changes not everytime when component re-renders so we will use useCallback

import React, { useState, useMemo } from 'react'

function factorialOf(n) {
    console.log('factorialOf(n) called!');
    return n <= 0 ? 1 : n * factorialOf(n - 1);
}

const main = () => {
    const [number, setNumber] = useState(2)
    const factorial = useMemo(() => factorialOf(number), [number])
    const [count, setCount] = useState(0)

    return (
        <div className='flex flex-col justify-center items-center'>
            {factorial}
            <button className='bg-gray-200 p-2 m-2' onClick={() => setNumber(number + 1)}>+</button>
            {count} <button className='bg-gray-200 p-2 m-2' onClick={() => setCount(count + 1)}>+</button>
        </div>
    )
}

export default main;

Image description


useRef()

lets console log useRef and see what it returns

console.log(useRef(100)); // this will return something like this ๐Ÿ‘‰๐Ÿป {current: 100}

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

when you compare an normal object with itself in useEffect after a rerender those are not the same, and that will trigger the useEffect on that object you can run this code ๐Ÿ‘‡๐Ÿป and try out.

import { useEffect, useState, useRef } from "react";

const component = () => {
    const obj1 = { hi: 100 };
    const obj2 = useRef({ hi: 100 });
    console.log(obj1 === obj2.current);

    const [state, setState] = useState(() => {
        return 1;
    });

    useEffect(() => {
        console.log('obj1 changed | ', obj1);
    }, [obj1])

    useEffect(() => {
        console.log('obj2 changed | ', obj2.current);
    }, [obj2])


    return (
        <div onClick={() => {
            setState((value) => {
                return value + 1;
            });
        }} className="w-screen h-screen flex justify-center items-center text-4xl font-extralight">
            {state}
        </div>
    )
}

export default component;

Image description

you can also use useState to work similar to useRef const obj = useState({current:10})[0];

๐Ÿฅณ๐Ÿฅณ๐Ÿฅณ๐Ÿฅณ๐ŸŽŠ๐ŸŽŠ๐ŸŽŠ๐ŸŽŠ hurrayyyyy!!!! you have covered all important hooks.

Connect me on Twitter :- Twitter ๐Ÿค๐Ÿป

Do check out my Github for amazing projects:- Github ๐Ÿค๐Ÿป

Connect me on LinkedIn :- Linkedin ๐Ÿค๐Ÿป