Stand-out Features of React Hooks You Should Know

Stand-out Features of React Hooks You Should Know

In this blog we will learn about the Hooks. First of all let us answer the question "Why we need Hooks ?"

There are many facets to React's class-based components that make it messy, confusing, and difficult to understand for humans and machines. Before React 16.8, class-based components were required for any project requiring states, lifecycle methods, or other critical functionality. Hooks introduced in React 16.8 changed all this, as well as addressed three major problems of class components: wrapper hell, huge components, and confusing classes. There is no doubt that hooks are game changers. As a result, React is now simpler, neater, easier to write, debug, and is also easier to learn.

useState()

useState allows us to make our components stateful. Whereas this previously required using a class component, hooks give us the ability to write it using just functions. It allows us to have more flexible components.

import { useState } from "react";

const StateComponent = () => {
  const [isGreen, setIsGreen] = useState(true);

  return (
    <h1
      onClick={() => setIsGreen(!isGreen)}
      style={{ color: isGreen ? "limegreen" : "crimson" }}
    >
      useState Example
    </h1>
  );
};

export default StateComponent;

Seeing the above code , useState is imported as import { useState } from "react";

Inside the StateComponent , we have declared the useState hook. It returns the pair of values, which are isGreen and setIsGreen . We have initialize the isGreen to true, by passing it as the argument in useState . The second item is itself a function, which will help to update isGreen .When the user clicks, we call setIsGreen with a new value. React will then re-render the StateComponent component, passing the new isGreen value to it.

We can use Multiple State Variables, but it will make our code lengthy. If possible try to put them in Object or in Array. However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.

useEffect()

It tells the React that your component needs to do something after render like Data fetching, DOM mutations , logging etc. It is called inside the function because hooks embrace Javascript Closures.

import { useState, useEffect } from "react";

const EffectComponent = () => {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    const timer = setTimeout(() => setTime(new Date()), 1000);
    return () => clearTimeout(timer);
  });

  return <h1>useEffect Example: {time.toLocaleTimeString()}</h1>;
};

export default EffectComponent;

We declare time state variable. When React renders our component, it will remember the effect we used and it will run effect after updating the DOM. This happens for every render, including the first one.

You could provide a second parameter of [] to useEffect (after the function) which would make it only update once. This second array is a list of dependencies: only re-run this effect if one of these parameters changed. In our case, we want to run after every render so we don't give it this second parameter.

React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.

useContext()

What is “prop drilling” or “data tunneling” ?

Basically it is passing the data through several nested children components, in a bid to deliver this data to a deeply-nested component. Problem with this method there are some components in between which are just only used as medium to pass the data, untill it reaches the destination. This can cause major issues with component reusability and app performance.

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

const UserContext = createContext([
  {
    firstName: "Bob",
    lastName: "Bobberson",
    suffix: 1,
    email: "bobbobberson@example.com"
  },
  (obj) => obj
]);

const LevelFive = () => {
  const [user, setUser] = useContext(UserContext);

  return (
    <div>
      <h5>{`${user.firstName} ${user.lastName} the ${user.suffix} born`}</h5>
      <button
        onClick={() => {
          setUser(Object.assign({}, user, { suffix: user.suffix + 1 }));
        }}
      >
        Increment
      </button>
    </div>
  );
};

const LevelFour = () => (
  <div>
    <h4>fourth level</h4>
    <LevelFive />
  </div>
);

const LevelThree = () => (
  <div>
    <h3>third level</h3>
    <LevelFour />
  </div>
);

const LevelTwo = () => (
  <div>
    <h2>second level</h2>
    <LevelThree />
  </div>
);

const ContextComponent = () => {
  const userState = useState({
    firstName: "James",
    lastName: "Jameson",
    suffix: 1,
    email: "jamesjameson@example.com"
  });

  return (
    <UserContext.Provider value={userState}>
      <h1>first level</h1>
      <LevelTwo />
    </UserContext.Provider>
  );
};

export default ContextComponent;

As you can see user object is not needed by the LevelTwo, LevelThree, and LevelFour component. Here Context allows you to create a wormhole where stuff goes in and a wormhole in a child component where the same data comes out and the stuff in the middle doesn’t know it’s there.

Now that data is available anywhere inside of the UserContext.Provider. useContext just pulls that data out when given a Context object as a parameter. You don't have to use useState and useContext together (the data can be any shape, not just useState-shaped) but I find it convenient when child components need to be able to update the context as well.

In general, context adds a decent amount of complexity to an app. A bit of prop drilling is fine. Only put things in context that are truly application-wide state like user information or auth keys and then use local state for the rest.

useMemo()

Lets talk about the useMemo(). First of all it is a Performance Optimization. It is used for mainly to avoid Computationally Expensive Calculation. Second it returns the memoized value. That means it stores the value of expensive function calls and returns them immediately when same input occur again.

import { useState, useMemo } from "react";

const fibonacci = (n) => {
  if (n <= 1) {
    return 1;
  }

  return fibonacci(n - 1) + fibonacci(n - 2);
};

const MemoComponent = () => {
  const [num, setNum] = useState(1);
  const [isGreen, setIsGreen] = useState(true);
  const fib = useMemo(() => fibonacci(num), [num]);

  return (
    <div>
      <h1
        onClick={() => setIsGreen(!isGreen)}
        style={{ color: isGreen ? "limegreen" : "crimson" }}
      >
        useMemo Example
      </h1>
      <h2>
        Fibonacci of {num} is {fib}
      </h2>
      <button onClick={() => setNum(num + 1)}></button>
    </div>
  );
};

export default MemoComponent;

As you can see from above code, we have tried to calculate Fibonacci of num. The calculation for 1 to 30 is easy for the computer. But as it once starts to move above 30+ it gets computationally expensive. As it will just cause pause and jank. But by using the useMemo , things become quite easy for computation. As it stores the value of the previous memoized answer. Which will help to calculate the Fibonacci number fast , if that same number appears again.

As you can see there is one dependency array num in it const fib = useMemo(() => fibonacci(num), [num]). It will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.

If no array is provided, a new value will be computed on every render.

useLayoutEffect()

With useEffect, your effect is scheduled to happen later, you actually don’t have a guarantee of when that it is. Because it is asynchronous. But with useLayoutEffect() is that you are guaranteed that it’s synchronous its next thing that’s gonna happen.

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

const LayoutEffectComponent = () => {
  const [width, setWidth] = useState(0);
  const [height, setHeight] = useState(0);
  const el = useRef();

  useLayoutEffect(() => {
    setWidth(el.current.clientWidth);
    setHeight(el.current.clientHeight);
  });

  return (
    <div>
      <h1>useLayoutEffect Example</h1>
      <h2>textarea width: {width}px</h2>
      <h2>textarea height: {height}px</h2>
      <textarea
        onClick={() => {
          setWidth(0);
        }}
        ref={el}
      />
    </div>
  );
};

export default LayoutEffectComponent;

The only time you should be using useLayoutEffect is to measure DOM nodes for things like animations. In the example, I measure the textarea after every time you click on it (the onClick is to force a re-render.) This means you're running render twice but it's also necessary to be able to capture the correct measurments.

Read the below references to understand Prop-drilling and the other Hooks in detail.

References