Telerik blogs

The Hooks API in React provides a few built-in hooks—which we can either use in components or in our own hooks with self-contained reusable logic.

React is a JavaScript library used for creating interactive web frontend applications. It is one of the most popular libraries because of its easy-to-use API.

Most recent versions of React provide the Hooks API, which lets us create frontend JavaScript apps with function components and the logic behind them. React provides a few built-in hooks which we can use in components or use them to create our own hooks with self-contained reusable logic.

In this article, we will look at how to create our own custom React hooks.

Basic React Hooks

Hooks are the basic building blocks of the Hooks API. They are functions that we call in components or custom hooks that let us add dynamic logic to components or custom hooks.

React provides a few basic hooks that let us add logic to our React apps. By convention, hooks all start with use in their names.

They can only be called at the top level of components and custom hooks so they can all be called in the order they are listed.

Some basic hooks include the useState, useEffect, useRef and useMemo hooks.

useState Hook

The useState hook lets us store states in our React components or custom hooks.

It returns an array with the state and state setter function.

For instance, we can use it by writing:

import { useState } from "react";

export default function App() {
  const [number, setNumber] = useState(Math.random());
  return (
    <div className="App">
      <button onClick={() => setNumber(Math.random())}>
        pick random number
      </button>
      <div>{number}</div>
    </div>
  );
}

to call the useState hook and set the initial value of the number state to a random number returned by Math.random.

We call the setNumber function in the click event handler to set the number state to a new random number value.

Also, we can update the state value based on the previous value of the state. To do this, we call the state setter function with a callback that takes the previous state value.

For example, we write:

import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <div className="App">
      <button onClick={() => setCount((oldCount) => oldCount + 1)}>
        increment count
      </button>
      <div>{count}</div>
    </div>
  );
}

to call useState to return the count state and the setCount function.

We add a button that calls setCount with a callback that takes the old value of count and returns the latest count value, which is the old count value plus 1.

Then when we click the button, we see the incremented count value displayed.

useEffect Hook

The useEffect hook lets us commit side effects in our React components or custom hooks.

It takes a callback that’s called whenever the states or props it is watching have changed if an array of states and props it is watching is set as the second argument. Otherwise, the callback is called on every render if no second argument is set. If we pass in an empty array as its second argument, then the callback is called only when the component mounts.

For instance, we can use it to watch for clicks on the whole screen.

To do this, we write:

import { useEffect, useState } from "react";

export default function App() {
  const [mouseLoc, setMouseLoc] = useState({});
  const { pageX, pageY } = mouseLoc;

  useEffect(() => {
    const onMouseMove = (e) => {
      const { pageX, pageY } = e;
      setMouseLoc({ pageX, pageY });
    };
    window.addEventListener("mousemove", onMouseMove);
    return () => window.removeEventListener("mousemove", onMouseMove);
  }, []);

  return (
    <div className="App">
      {pageX}, {pageY}
    </div>
  );
}

to call the useState hook to define the mouseLoc state.

Then we get the pageX and pageY values, which are the x and y coordinates of the mouse cursor location on the screen.

Next, we call the useEffect hook with a callback that calls window.addEventListener with 'mousemove' and the onMouseMove function to add the onMouseMove function as the mousemove event listener for the window, which is the current browser tab.

We return a function in the useEffect hook callback that’s called whenever a state or prop being watched is changed or when the component is unmounted when an empty array is passed in as the second argument into useEffect.

We call window.removeEventListener to remove the onMouseMove as the mousemove event listener. We then display the pageX and pageY values on the screen. As a result, when we move the mouse around, we see the mouse cursor’s coordinates displayed on the screen.

useRef Hook

The useRef hook lets us store values that we want to keep when the component is re-rendered.

For instance, we write:

import { useEffect, useRef } from "react";

export default function App() {
  const ref = useRef();
  useEffect(() => {
    ref.current.focus();
  }, []);

  return (
    <div className="App">
      <input ref={ref} />
    </div>
  );
}

to call the useRef hook to create a ref.

Then we can assign it a value by passing the ref to the ref prop of an HTML element or component.

We can also assign a value we want to keep when the component re-renders as the value of the ref.current property.

In the useEffect hook, we call ref.current.focus to set focus on the input since ref.current has the input element as its value.

This is because we set the ref prop of the input to the ref returned by the useRef hook.

useMemo Hook

The useMemo hook lets us cache values computed from states.

import { useMemo, useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const [count2, setCount2] = useState(0);

  const totalCount = useMemo(() => {
    return count + count2;
  }, [count, count2]);

  return (
    <div className="App">
      <button onClick={() => setCount((oldCount) => oldCount + 1)}>
        increment count
      </button>
      <button onClick={() => setCount2((oldCount2) => oldCount2 + 1)}>
        increment count 2
      </button>
      <div>{totalCount}</div>
    </div>
  );
}

to define the count and count2 states with the useState hook.

Then we create the totalCount derived state with the useMemo hook.

We call it with a callback that returns the sum of count and count2 to set that as the value of totalCount.

And we call useMemo with an array with count and count2 in it to make the useMemo recalculate the sum when either of the values change.

Therefore, when we click either button, we see the totalCount update.

Creating Custom Hooks

There are a few other hooks that React comes with. We can use any hooks to create our own custom hooks. Let’s see how.

We can create hooks and use them easily since they are all pure functions. This means that they take inputs and return outputs derived from the inputs. Therefore, it is very easy for us to create and use hooks in our components.

To start, we create the useScreenSize.js file. Then we write:

import { useEffect, useState } from "react";

const { innerWidth, innerHeight } = window;

export default () => {
  const [screenSize, setScreenSize] = useState({
    innerWidth,
    innerHeight,
  });

  useEffect(() => {
    const onResize = (e) => {
      const { innerWidth, innerHeight } = e.target;
      setScreenSize({ innerWidth, innerHeight });
    };
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);
  return screenSize;
};

to create the useScreenSize hook.

We call useState to create the screenSize state.

Then we call the useEffect hook with a callback that listens for the resize event with window.addEventListener.

We use the onResize function as the resize event listener. In it, we get the window width and height from the e.target.innerWidth and e.target.innerHeight properties respectively.

We call setScreenSize to set the screenSize state to an object with the window innerWidth and innerHeight values in pixels.

And we return a callback that calls removeEventListener to remove the resize event listener when we unmount the component.

Finally, we return the screenSize state so we can access it in our components.

Next in App.js, we write:

import useScreenSize from "./useScreenSize";

export default function App() {
  const { innerWidth, innerHeight } = useScreenSize();

  return (
    <div className="App">
      <div>
        {innerWidth}x{innerHeight}
      </div>
    </div>
  );
}

to import and call the useScreenSize hook created earlier.

We get the innerWidth and innerHeight properties from the object returned by the hook with the destructuring syntax. Then we display them on the screen.

As a result, when we resize the browser tab or window, we see the latest width and height of the browser tab or window.

As we can see, we just return the values we want in our custom hook, and we can access them easily from a component or another hook. This is the power of the React Hooks API.

We can easily compose logic since they are all self-contained and we just use their return value without needing to worry about what they do.

Also, this means we can mock them easily in our tests since we just need to mock the return value of the hook in our component tests.

Conclusion

The React Hooks API lets us create frontend JavaScript apps with function components and logic.

React provides a few built-in hooks—including the useState, useEffect, useRef and useMemo hooks—which we can use in components or use them to create our own hooks with self-contained reusable logic.


About the Author

John Au-Yeung

John Au-Yeung is a frontend developer with 6+ years of experience. He is an avid blogger (visit his site at https://thewebdev.info/) and the author of Vue.js 3 By Example.

Related Posts

Comments

Comments are disabled in preview mode.