DEV Community

Cover image for All React Hooks and Concepts In A Single Post!! ๐Ÿค—
Ritesh Kumar
Ritesh Kumar

Posted on

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

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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;
});
Enter fullscreen mode Exit fullscreen mode

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;
      });
}} 
Enter fullscreen mode Exit fullscreen mode

useEffect()

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

useEffect(()=>{},[])
Enter fullscreen mode Exit fullscreen mode
// 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');
})
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

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();
    }, []);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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>
    )
}

Enter fullscreen mode Exit fullscreen mode

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 ๐Ÿค๐Ÿป

Read my another article :- Authentication in nodejs with mongodb bcrypt and jwt web tokens

Top comments (12)

Collapse
 
spock123 profile image
Lars Rye Jeppesen

My God this really shows how horrible React has become. It's a mess compared to modern frameworks. Great article though, thanks.

Collapse
 
morgboer profile image
Riaan Pietersen

Could you link us to some of the mentioned "modern frameworks", please?

Collapse
 
spock123 profile image
Lars Rye Jeppesen

One example could be frameworks that are actually written in Typescript.
Frameworks that utilize observables and not promise-based logic.
Frameworks that don't require a "root component" in components, they can actually handle templates without a root component that doesn't do anything.
Frameworks that embrace standard HTML, not forcing absurd syntax like "className" just to set at class.

Just the clutter of having to use horrible syntax for useEffect() to have something running at the start of a component, is absolutely nuts.

These are just a few examples.

Thread Thread
 
morgboer profile image
Riaan Pietersen

Sounds great! Would you mind posting a URL to one or two of these?

Collapse
 
nyctonio profile image
Ritesh Kumar

Lol actually there are a lot of advantages of React which make it 100% worth the disadvantages

Thread Thread
 
morgboer profile image
Riaan Pietersen

Agree Ritesh. Lars is just flaming.. there are definitely use cases for running code at the start of a component. I was honestly hoping he would post a URL to these phantom "frameworks" he refers to.. ๐Ÿคทโ€โ™‚๏ธ

Thread Thread
 
spock123 profile image
Lars Rye Jeppesen

Flaming? that was not my intention. Of course there's use for running code at the start of a component.

Collapse
 
karranx profile image
karranx

Great post ๐Ÿ˜Š

Collapse
 
nyctonio profile image
Ritesh Kumar

Thank you karranx

Collapse
 
minhazhalim profile image
Minhaz Halim (Zim)

Great tutorial

Collapse
 
harshhhdev profile image
Info Comment hidden by post author - thread only accessible via permalink
Harsh Singh

Hey,

I've removed the Next.js tag from this post. In the future, kindly only use the tag for topics specificly related to Next.js, as opposed to JavaScript or React as a whole. I realise I'm a bit late as you've already gotten then Next.js author of the week badge, but please remember next time. You're taking away an opportunity to earn that badge from someone who actually wrote a post about Next.js.

Collapse
 
beedev profile image
Mike

Great Article

Some comments have been hidden by the post's author - find out more