Fix – Pass an inline function instead (useCallback & useMemo)

Have you also encountered this error “React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead react-hooks/exhaustive-deps” from the ESlint?.

Fix - Pass an inline function instead (usecallback & useMemo)

In this article, we will see what is this error and how to fix this for debouncing and throttling.

Let us say we have a functional component, that does debounced text search on the input.

import React, { useState, useCallback } from "react";

const DebouncedSearch = () => {
  const [searchText, setSearchText] = useState("");

  const _debounce = (func, delay) => {
    let inDebounce;
    return function () {
      const context = this;
      const args = arguments;
      clearTimeout(inDebounce);
      inDebounce = setTimeout(() => func.apply(context, args), delay);
    };
  };

  const makeNetworkCall = (e) => {
    console.log(e, "Making an API call");
  };

  const debounce = useCallback(_debounce(makeNetworkCall, 2000), []);

  const handleChange = (e) => {
    setSearchText(e.target.value);
    debounce(e.target.value);
  };

  return (
    <div>
      <input type="text" onChange={handleChange} value={searchText} />
    </div>
  );
};

In this component, we have the _debounce that returns another function when invoked, this _debounce function is passed to the useCallback() hook.

useCallback() caches the function so that it should be re-created on every re-render saving memory usage, it expects to know the definition of the function it is caching because it can take the dependencies of the function as input and re-create the function if dependencies changes.

Because useCallback() is not sure about the function returned by _debounce and what parameters it will take an input, it throws the linting error “React Hook useCallback received a function whose dependencies are unknown“.

To solve this it asks us to “Pass an inline function instead“.

But what is an inline function?, An inline function is a function that is defined within the useCallback() hook.

const cachedFn = useCallback(function(){
  // I am an inline function
}, []);

Fix the issue “Pass an inline function instead”

To fix the issue, we have to closely monitor the definition of the _debounce function. The useCallback() caches the returned function from the _debounce which means we have to define this returned function as an inline function.

But this returned function is creating the closure with the parent function and accessing its arguments.

The most logical way to fix this is by creating a custom hook useDebounce() that will form the closure as well as we can use the useCallback with inline function.

import React, { useState, useCallback, useRef } from "react";

const useDebounce = (func, delay) => {
  const inDebounce = useRef();

  const debounce = useCallback(
    function () {
      const context = this;
      const args = arguments;
      clearTimeout(inDebounce.current);
      inDebounce.current = setTimeout(() => func.apply(context, args), delay);
    },
    [func, delay]
  );

  return debounce;
};

const DebouncedSearch = () => {
  const [searchText, setSearchText] = useState("");

  const makeNetworkCall = (e) => {
    console.log(e, "Making an API call");
  };

  const debounce = useDebounce(makeNetworkCall, 2000);

  const handleChange = (e) => {
    setSearchText(e.target.value);
    debounce(e.target.value);
  };

  return (
    <div>
      <input type="text" onChange={handleChange} value={searchText} />
    </div>
  );
};

export default DebouncedSearch;

Fixing in the useMemo

Unlike useCallback, useMemo() caches the result of the function that it receives and takes a dependency array as input. If any of the dependencies changes, it will re-render the function.

const cachedVal = useMemo(function(){
return 10 + 20;
}, []);

We can fix the same error for useMemo by passing an inline function and if there is a use of closure then by creating a custom hook.