Categories
JavaScript React

Built-in React Hooks — useCallback, useMemo and Refs

Spread the love

React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.

In this article, we look at the useCallback, useMemo, useRef, and useImperativeHandle hooks.

useCallback

We can use the useCallback hook to return a memoized callback.

It takes a callback function as the first argument, and an array of value that changes for the callback in the first argument to be called.

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

For example, we can use it as follows:

function App() {  
  const [count, setCount] = React.useState(0);  
  const memoizedIncrement = React.useCallback(() => {  
    setCount(count => count + 1);  
  }, [setCount]); 

  const memoizedDecrement = React.useCallback(() => {  
    setCount(count => count - 1);  
  }, [setCount]); 

  return (  
    <>  
      Count: {count}  
      <button onClick={memoizedDecrement}>Decrement</button>  
      <button onClick={memoizedIncrement}>Increment</button>  
    </>  
  );  
}

In the code above, we used the useCallback hook to create 2 new functions to let us update the count . We have setCount(count => count + 1) and setCount(count => count — 1) in the callback of useCallback .

And we cache the setCount function so that React won’t always return a new one in every render unless setCount changes.

Then in the onClick handlers, we use them as usual.

Every value referenced inside the callback should also appear in the dependencies array.

useMemo

useMemo caches values that are computed from a function. The first argument is a function that computes the value, and the second argument is an array with the dependencies that are used to compute the returned value.

useMemo runs during rendering. It’s used as a performance optimization. React may forget previously memoized value and recalculate them on the next render in cases it needs to free memory for offscreen components for example.

Therefore, we should write code that works with useMemo and then add it to optimize performance.

For example, we can use it as follows:

function App() {  
  const [count, setCount] = React.useState(0);  
  const doubleCount = React.useMemo(() => 2 * count, [count]); 

  return (  
    <>  
      Double Count: {doubleCount}  
      <button onClick={() => setCount(oldCount => oldCount - 1)}>  
        Decrement  
      </button>  
      <button onClick={() => setCount(oldCount => oldCount + 1)}>  
        Increment  
      </button>  
    </>  
  );  
}

In the code above, we use the useMemo hook by passing in a function that returns 2 times the count , and pass in an array with count inside as the 2nd argument.

useMemo caches the value of doubleCount until count changes.

Then we pass in functions to call setCount in the onClick props of the buttons.

Finally, we display the doubleCount value on the screen, which updates when we click on the buttons.

useRef

The useRef hook returns a mutable ref whose current property is initialized to the passed argument. The returned object will persist for the full lifetime of the component.

For example, we can use it as follows:

function App() {  
  const inputEl = React.useRef(null);  
  React.useEffect(() => {  
    inputEl.current.focus();  
  }, []);  
  return (  
    <>  
      <input ref={inputEl} type="text" />  
    </>  
  );  
}

In the code above, we created the inputEl ref by calling useRef with the value null .

Then once we pass inputEl as the ref of the input, we set inputEl.current to the input element’s DOM object.

Therefore, we can call focus on it to focus the input in the useEffect callback, which is only run during initial render, since we passed in an empty array to the second argument.

It can be used to keep any mutable value around. useRef creates a plain Javascript object.

The only difference between useRef() and creating a { current: ... } object ourselves is that useRef will give us the same ref object on every render.

useRef doesn’t notify us when the content changes.

useImperativeHandle

useImperativeHandle customizes the instance value that’s exposed to parent components when using a ref .

For example, we can use it as follows:

function Button(props, ref) {  
  const buttonRef = React.useRef();  
  React.useImperativeHandle(ref, () => ({  
    focus: () => {  
      buttonRef.current.focus();  
    }  
  }));  
  return <button ref={buttonRef}>Button</button>;  
}  
Button = React.forwardRef(Button);

function App() {  
  const buttonRef = React.useRef(null);  
  React.useEffect(() => {  
    buttonRef.current.focus();  
  }, []);  
  return (  
    <>  
      <Button ref={buttonRef} />  
    </>  
  );  
}

In the code above, we have:

React.useImperativeHandle(ref, () => ({  
    focus: () => {  
      buttonRef.current.focus();  
    }  
  }));

to customize the focus method to call buttonRef.current.focus(); .

Then we pass buttonRef in Button to the button ‘s ref as follows:

<button ref={buttonRef}>Button</button>;

Then to make the ref accessible to App , we run:

Button = React.forwardRef(Button);

Then in App , we run:

const buttonRef = React.useRef(null);

to create the ref and:

<Button ref={buttonRef} />

to set buttonRef to our exposed Button ‘s buttonRef after calling forwardRef to expose it to App .

Then we run:

React.useEffect(() => {  
    buttonRef.current.focus();  
  }, \[\]);

to focus the Button component’s button when App first renders.

Conclusion

We can use the useCallback hook to return a memoized callback, which we can call. It takes a callback as an argument and an array of dependencies that were referenced in the callback as the second argument.

useMemo caches values that are computed. It takes a function that returns a value as the first argument, and an array of values that the function depends on as the second argument.

useRef returns a mutable object whose current property is initialized to the initial value and can be passed into the ref prop of an element to set current to the DOM element.

useImperativeHandle customizes the behavior of the DOM element methods of the element that’s exposed via forwardRef to the parent component.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *