DEV Community

Akshay Palekar
Akshay Palekar

Posted on

How React Hooks Work in simple words?

Let's start with what is a Hook?

A Hook is a react function that lets you use state and react features from a function based component. Hooks let you use the functions instead of switching between HOCs, Classes, and functions. As Hooks are regular Javascript functions, thus you can use the built-in Hooks and create your own custom one. So the solution to your problem will be a "one-liner" now.

Before we know how React hooks work, let us define what closure is.

“Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.”

In the upcoming examples there will be Closures, so that made me define it.

How does the useState work?

To illustrate how useState works, let us consider an example.

const OurReact = (function(){
    let val; // ‘val’ stores the value in the module scope 
        return {
            render(Component) {
               const Comp = Component();
               Comp.render();
               return Comp;
            },
            useState(initialVal) {
               val = val || initialVal;// Assigns a new value every run 
               function setState(newVal) {
                   val = newVal;
               }
               return [val, setState];
            },
        };  
})();
Enter fullscreen mode Exit fullscreen mode

Here we have used the Module Pattern in the above example to create our own small React clone. So if you want to access the useState then you need to access it as OurReact.useState(...). Like in React, to keep track of the Component State, we are making use of the ‘val’ variable (To keep it simple, it tracks only one component.). As you can see, the useState is inside a closure. Now let us use the above code.

function Character () {
    const [characteName, setCharacterName] = OurReact.useState(Mario); // default value to ‘Mario’
    return{
        changeName: (charName) => setCharacterName(charName),
        render: () => console.log(Rendered character:, characteName),
    }
}
let App;
App = OurReact.render(Character);  // Rendered character: Mario
App.changeName(Luigi);
App = OurReact.render(Character); // Rendered character: Luigi
Enter fullscreen mode Exit fullscreen mode

So this was the simple code to Illustrate useState hook.

Now how does useEffect works?

Whenever you want to do something as a side effect after each render(i.e. Make an API Call or Check component has the data or not), you can pass such effects to the useEffect. If you are familiar with Class-based components, then useEffect serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.

function Example() {
    const [characterName, setCharacterName] = useState(‘Mario’);
    // Similar to componentDidMount and componentDidUpdate:
    useEffect(()=>{
       document.title = `Character name ${characterName}`;
    });
    return(
       <div>
          <p>Character : {characterName}</p>
          <input type=’text’ value={characterName} onChange={e => setCharacterName(e.target.value)} />
       </div>
    );
};

Enter fullscreen mode Exit fullscreen mode

Let us replicate the useEffect hook by extending our tiny react clone that we created before.

const OurReact = (function(){
    let val, deps; // A new variable ‘deps’ to hold our dependencies
    return {
        render(Component) {
            const Comp = Component();
            Comp.render();
            Return Comp;
        },
        useEffect(callBack, dependencyArr){
           const hasNoDependency = !dependencyArr,
                 hasDependencyChanged = deps ? !dependencyArr.every((el, i) => el === deps[i]) : true;
           if (hasNoDependency  || hasDependencyChanged ) {
               callback();
               deps = dependencyArr;
           }
        },
        useState(initialVal) {
           val = val || initialVal;
           function setState(newVal) {
              val = newVal;
           };
           return [val, setState];
        },
    };  
})();

Enter fullscreen mode Exit fullscreen mode

Usage:

function Character () {
    const [characteName, setCharacterName] = OurReact.useState(Mario);
    OurReact.useEffect(() => {
        console.log(effect called , characterName);
    }, [characteName])
    return{
        changeName: (charName) => setCharacterName(charName),
        noopFunction: () => setCharacterName(characteName),  // Similar to Jquery.noop that does nothing.
        render: () => console.log(Rendered character:, characteName),
    }
}

let App;
App = OurReact.render(Character);
//   effect called Mario
// Rendered character: Mario

App.changeName(Luigi);
App = OurReact.render(Character); 
// effect called Luigi
// Rendered character: Luigi

App.noopFunction()
App = OurReact.render(Character); 
// No effects
// Rendered character: Luigi

App.changeName(Yoshi);
App = OurReact.render(Character); 
// effect called Yoshi
// Rendered character: Yoshi

Enter fullscreen mode Exit fullscreen mode

The useEffect runs whenever there is a change in the dependencies, thus we have introduced a new variable in the above example as ‘deps’. So that was the simple explanation of the useEffect Hook.

We know that Hook implementation has thousands of lines of code in it. But hopefully, you got an idea of how things work inside.

Rules to follow while using the Hooks.

  • Hooks should always be called at the Top Level.

By following this rule, you are making sure that Hooks are always called in the same order as they were declared each time your component renders. (Remember that don't ever call the hooks inside the functions that are nested and also inside the loops.)

Explanation:

functions character() {
    const [characterName, setCharacterName] = useState(Mario);
    useEffect(function storeTheCharacter(){
        localStorage.setItem(formData, characterName);
    });
    const [characterAbility, setCharacterAbility] = useState(Fire flower); 
    useEffect(function displayDetails(){
    document.getElementById(characterView).innerHTML(`Character: ${characterName}, Ability: ${ characterAbility}`)
    });
}
Enter fullscreen mode Exit fullscreen mode

We might have a question that, how does React know which state fits with which useState? The answer is just what we discussed, we always need to call the hooks in the same order as they were declared. And if we call the hooks inside a loop or the order of the hook changes, React will get confused about how to maintain the state for our component.

// 1st Render
useState(Mario);  // Initialize the ‘characterName’ state variable to ‘Mario’
useEffect(storeTheCharacter);  // Adding an effect to store the ‘characterName’ to the localStorage
useState(Fire Flower);  // Initialize the ‘characterAbility’ state variable with 'Active'
useEffect(displayDetails);  // Adding an effect to update the displaying data

// 2nd render
useState(Mario);  // Read the characterName state variable (argument is ignored)
useEffect(storeTheCharacter);  // Replace the effect for persisting the form
useState(Fire Flower);  // Read the characterAbilities state variable (argument is ignored)
useEffect(displayDetails);  // Replace the effect for updating the displaying data

Enter fullscreen mode Exit fullscreen mode

Since the order of the hooks is maintained, React will be able to Maintain our component’s state.

What if we call a hook with a condition?

if( characterName !== ‘’ ){
    useEffect(function storeTheCharacter () {
            localStorage.setItem('formData', characterName);
    });
}

Enter fullscreen mode Exit fullscreen mode

Here we are breaking the first rule of the Hook in a condition. Let’s see what happens when the condition is ‘false’, the hook is skipped during the rendering, and the order of the Hook call becomes different.
i.e.

useState(Mario) // 1. Read the name state variable (argument is ignored)
// useEffect(storeTheCharacter)  // This Hook was skipped!
useState(Fire Flower)  // 2 (but was 3). Fail to read the surname state variable
useEffect(updateTitle)  // 3 (but was 4). Fail to replace the effect
Enter fullscreen mode Exit fullscreen mode

React fails to recognize what to return for the second useState Hook call. React expected that the second Hook call in this component corresponds to the 'storeTheCharacter' effect, just like during the previous render, but it doesn’t anymore. From that point, every next Hook call after the one we skipped would also shift by one, leading to bugs. So that's the reason why hooks are always called on the top level of our component.

  • Hooks should always be called from React Functions.

Don't call Hooks from regular JavaScript functions. Instead, you can

  1. Call Hooks from React Function components.
  2. Call Hooks from custom Hooks.

Explanation:

import { useState } from react;
const lives = 3;
const isGameOver = (noOfDeaths) =>{
    const livesLeft = lives  noOfDeaths;
    const [characterName, setCharacterName] = useState(Mario);
        if (livesLeft === 0) { return Game Over’’; } else { return Continue; }
}

Enter fullscreen mode Exit fullscreen mode

This doesn't make any sense to call the useState inside a regular function.

So those were the rules that you need to follow while using the hooks.


Hope you guys got a clear idea about how things are working inside the React Hooks API and what rules we need to follow while using the hooks. Nowadays hooks are a crucial part of most of the react components. If you guys got any questions related to this post, feel free to shoot them in the comment box.Thank you

Top comments (2)

Collapse
 
amit1910 profile image
amit1910

Very well explained

Collapse
 
ownalazzam profile image
Own Alazzam

Good Explain But How React Replace The Hook ??
If You Using A Clinet-Side Router Like React-Router React Reaplace Hooks Data How This Is Made???