DEV Community

Cover image for React Hooks
sv0012
sv0012

Posted on

React Hooks

Hooks are functions that let you "hook into" React state and lifecycle features from function components.Hooks don't work inside classes,they let you use React without classes.You can also create your own Hooks to reuse stateful behavior between different components.

Lets take look at the various hooks :

  • Basic Hooks
  1. useState
  2. useEffect
  3. useContext
  • Additional Hooks
  1. useReducer
  2. useMemo
  3. useCallback
  4. useRef
  5. useLayoutEffect
  6. useImperativeHandle
  7. useDebugValue

useState

useState is a Hook that allows you to have state variables in functional components. You pass the initial state to this function and it returns a variable with the current state value (not necessarily the initial state) and another function to update this value.

Consider an example of a counter incrementing on click.


import React from "react";

const State = () => {

let counter = 0;

const increment = () => {
counter = counter+1;
console.log(counter);
};

return (
      <div>
         {counter}
         <button onClick={increment}>Increment</button>
      </div>
);
};

export default State;

Enter fullscreen mode Exit fullscreen mode

Considering the above example everything looks right, but when we do click on the increment the value shown wouldn't update.But why?
Is there something wrong with the function,variable?

But as you can see in the console the variable is incrementing but react isn't re-rendering the page to show the new value every time the value updates.

This is where the useState comes in.

Lets replace the counter variable with a variable that's defined by the state using the useState syntax.The counter is the variable which comes first and the setCounter is the function that is used to update the variable.The initial value of the state will be passed to the useState inside the parentheses.


import React from "react";

const State = () => {

const [counter,setCounter] = useState(0);

const increment = () => {
setCounter(counter + 1);
};

return (
      <div>
         {counter}
         <button onClick={increment}>Increment</button>
      </div>
);
};

export default State;


Enter fullscreen mode Exit fullscreen mode

So the setCounter updates the value and re-renders.

useEffect

The useEffect hook is just a function that will be called when the page re-renders,however it is very powerful as we can specify when we want to rerun the function based on the dependency array.

The useEffect has two parts basically the function and the dependency.When the values in the dependency changes the function will run again.

To understand this better lets consider an example where we want to fetch some data from a public api and display it on our screen.


import React, { useEffect, useState } from "react";
import axios from "axios";

function Effect() {
  const [data, setData] = useState("");
  const [count, setCount] = useState(0);

  useEffect(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/comments")
      .then((response) => {
        setData(response.data[0].email);
        console.log("API WAS CALLED");
      });
  }, []);

  return (
    <div>
      Hello World
      <h1>{data}</h1>
      <h1>{count}</h1>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click
      </button>
    </div>
  );
}

export default Effect;

Enter fullscreen mode Exit fullscreen mode

The data state contains the email of the first comment that was fetched from the api.

As the dependency array is empty useEffect will only run once.

The other state count is to better understand the useEffect.

Now even when we click on the button the page re-renders and updates the counter but when you check the console the "API WAS CALLED" message would have appeared only once as the dependency array is empty.If the dependency array had the count state whenever the state changed this useEffect function will run again.

Dependency array is very essential to the useEffect even if it is empty else it has the risk of being on an infinite loop if only the function was defined.

useContext

The useContext is to be used together with the context api.The context api allows us to manage the state very easily.

Lets imagine a component that has two different components in it.

One component sets the value and the other uses that value.

Lets take a look at the code.


//The parent component

import React, { useState, createContext } from "react";
import Login from "./Login";
import User from "./User";

export const AppContext = createContext(null);

function Context() {
  const [username, setUsername] = useState("");

  return (
    <AppContext.Provider value={{ username, setUsername }}>
      <Login />
      <User />
    </AppContext.Provider>
  );
}

export default Context;

Enter fullscreen mode Exit fullscreen mode

//Login child component

import React, { useContext } from "react";
import { AppContext } from "./Context";

function Login() {
  const { setUsername } = useContext(AppContext);

  return (
    <div>
      <input
        onChange={(event) => {
          setUsername(event.target.value);
        }}
      />
    </div>
  );
}

export default Login;

Enter fullscreen mode Exit fullscreen mode
//User child component

import React, { useContext } from "react";
import { AppContext } from "./Context";

function User() {
  const { username } = useContext(AppContext);

  return (
    <div>
      <h1>User: {username}</h1>
    </div>
  );
}

export default User;

Enter fullscreen mode Exit fullscreen mode

The createContext allows us to create a new context.

The AppContext.Provider is wrapped around the two component which makes the context values accessible to the components.

The state and setState are given as the values in the context provider so not only is the state accessible but it is also updatable using the set function.

As you can see in the components using the useContext we are obtaining the values of the context provided and using it in the component by de-structuring it.

useReducer

The useReducer is like a replacement or an alternative to the useState hook.

They allow the developers to create variables and when they are changed the page will re-render.

Lets consider the following example


import React, { useState } from "react";

const Reducer = () => {

const [count,setCount] = useState(0);
const [showText,setShowText] = useState(true);

return (
       <div>
       <h1>{count}</h1>
       <button onClick={()=>{
         setCount(count+1);
         setShowText(!showText);
         }}
       >Click Here</button>

       {showText && <p>This is a text</p>}
       </div>
       );
};

export default Reducer;

Enter fullscreen mode Exit fullscreen mode

So we can see that we have used the useState and that one of the state is a boolean that on every click changes and therefore shows the text on every alternate click.

Here we can see that on one click we are altering the value of two states.

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.

By using useReducer the state can be managed collectively and can be changed as we please.


import React, { useReducer } from "react";

const reducer = (state, action) => {
  switch (action.type) {
    case "INCREMENT":
      return { count: state.count + 1, showText: state.showText };
    case "toggleShowText":
      return { count: state.count, showText: !state.showText };
    default:
      return state;
  }
};

const Reducer = () => {
  const [state, dispatch] = useReducer(reducer, { count: 0, showText: true });

  return (
    <div>
      <h1>{state.count}</h1>
      <button
        onClick={() => {
          dispatch({ type: "INCREMENT" });
          dispatch({ type: "toggleShowText" });
        }}
      >
        Click Here
      </button>

      {state.showText && <p>This is a text</p>}
    </div>
  );
};

export default Reducer;

Enter fullscreen mode Exit fullscreen mode

The state is an object of all the states that is going to be changed.The dispatch is a function to be used to change the values of our state.

We have passed the reducer function the initial values of all the states to the useReducer.

The reducer function manages what happens to the state.It will take in two arguments the state and the action.As you can see we are using the action.type to determine the state changes to be performed.

useMemo

React has a built-in hook called useMemo that allows us to memoize expensive functions so that we can avoid calling them on every render.You simply pass in a function and an array of inputs and useMemo will only recompute the memoized value when one of the inputs has changed.

We need to keep in mind that the function passed to the useMemo runs during rendering and we shouldn't do anything there that we wouldn't do when we are rendering.

If an array isn't provided a new value will be computed on every render.

We should rely on useMemo as a performance optimization.We need to write code so that it still works without useMemo and only add it to further optimize the performance.


const memoizedValue = useMemo(()=>{ computeExpensiveValue(a,b),[a,b]);


Enter fullscreen mode Exit fullscreen mode

useCallback

The useCallback hooks is used when we have a component that has a child that keeps re-rendering without any need.Pass an inline callback and an array of dependencies,useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.

This is useful while passing callbacks to optimize child components that rely on reference equality to prevent unnecessary renders.


const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

Enter fullscreen mode Exit fullscreen mode

Note: useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

useRef

The useRef is a hook that allows to directly create a reference to the DOM element in the functional component.
The useRef returns a mutable ref object.

Lets imagine this scenario, you have a single component which as input,button and h1 tag and you want to be able to click on the button it should automatically focus on the input so that we can start typing a new name.Lets not worry about the logic for updating the new name and focus on useRef.


import React from "react";

function Ref() {

return (
       <div>
       <h1>Cris</h1>
       <input type="text" placeholder="Example..."/>
       <button>Change Name</button>
       </div>
       );
}

export default Ref;


Enter fullscreen mode Exit fullscreen mode

Now whenever we click on this button or whenever we take an action we should be able to focus on the input and this is where useRef comes in.The useRef hook comes in handy when we need to manipulate or add some functionality to certain DOM elements.


import React, { useRef } from "react";

function Ref() {
  const inputRef = useRef(null);

  const onClick = () => {
    inputRef.current.value = "";
  };
  return (
    <div>
      <h1>Cris</h1>
      <input type="text" placeholder="Ex..." ref={inputRef} />
      <button onClick={onClick}>Change Name</button>
    </div>
  );
}

export default Ref;

Enter fullscreen mode Exit fullscreen mode

The inputRef is given as a ref to the input tag and the current value property of the inputRef is accessed in the onClick function.

By setting it to empty string when we click the button after typing some data we can see that the typed data is set to null.

useRef is very helpful in accessing or manipulating the DOM and is to be used in such suitable situations.

useLayoutEffect

The useLayoutEffect is very similar to the useEffect,but it fires synchronously after all the DOM mutations.Use it to read layout from the DOM and synchronously re-render.Updates scheduled inside useLayoutEffect will be flushed synchronously,before the browser has a chance to paint the DOM.

When the useEffect is called it will show the stuff to user and then call the useEffect.It is only called after the page is rendered and we can hardly tell because it changes the state so fast.

The useEffect is called after everything is rendered in the page and shown to the user.But the useLayoutEffect is called before the data is actually printed to the user.


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

function LayoutEffect() {
  const inputRef = useRef(null);

  useLayoutEffect(() => {
    console.log(inputRef.current.value);
  }, []);

  useEffect(() => {
    inputRef.current.value = "HELLO";
  }, []);

  return (
    <div className="App">
      <input ref={inputRef} value="CRIS" style={{ width: 400, height: 60 }} />
    </div>
  );
}

export default LayoutEffect;

Enter fullscreen mode Exit fullscreen mode

As we can see that value of input is CRIS but because of the useEffect it will show HELLO.But when we see the console we can see that CRIS is printed.

The useLayoutEffect can be used in cases where we want to change the layout of our application before it prints to the user.

useImperativeHandle

The useImperativeHandle hooks works in the similar phase to the useRef hook but only it allows us to modify the instance that is going to be passed with the ref object which provides a reference to any DOM element.


useImperativeHandle(ref, createHandle, [deps])

Enter fullscreen mode Exit fullscreen mode

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

Enter fullscreen mode Exit fullscreen mode

The parent component that renders would be able to call inputRef.current.focus().

useDebugValue

useDebugValue is a simple inbuilt Hook that provides more information about the internal logic of a custom Hook within the React DevTools. It allows you to display additional, helpful information next to your custom Hooks, with optional formatting.


function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // Show a label in DevTools next to this Hook  // e.g. "FriendStatus: Online"  useDebugValue(isOnline ? 'Online' : 'Offline');
  return isOnline;
}

Enter fullscreen mode Exit fullscreen mode

The useDebugValue also accepts a formatting function as an optional second parameter.The function will only be called when the custom hook is inspected.It will receive the debug value as a parameter and return a formatted display value.

For example a custom Hook that returned a Date value could avoid calling the toDateString function unnecessarily by passing the following formatter:


useDebugValue(date, date => date.toDateString());

Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
scriptkavi profile image
ScriptKavi

Many early birds have already started using this custom hooks library
in their ReactJs/NextJs project.

Have you started using it?

scriptkavi/hooks

PS: Don't be a late bloomer :P