Skip to content
On this page

Memoized Functions in React (Advanced)

Functions that are defined in a React components are most of the time event handlers. However, because a React component is just a function itself, you can declare functions, function expressions, and arrow function expressions in a component too. In this section, we will introduce the concept of a memoized function by using React's useCallback Hook.

We will refactor the code upfront to use a memoized function and provide the explanations afterward. The refactoring consists of moving all the data fetching logic from the side-effect into a arrow function expression (A), wrapping this new function into React's useCallback hook (B), and invoking it in the useEffect hook (C):

src/App.jsx

jsx
const App = () => {
  ...

  // A
  const handleFetchStories = React.useCallback(() => { // B
    if (!searchTerm) return;

    dispatchStories({ type: 'STORIES_FETCH_INIT' });

    fetch(`${API_ENDPOINT}${searchTerm}`)
      .then((response) => response.json())
      .then((result) => {
        dispatchStories({
          type: 'STORIES_FETCH_SUCCESS',
          payload: result.hits,
        });
      })
      .catch(() =>
        dispatchStories({ type: 'STORIES_FETCH_FAILURE' })
      );
  }, [searchTerm]); // E

  React.useEffect(() => {
    handleFetchStories(); // C
  }, [handleFetchStories]); // D

  ...
};

At its core, the application behaves the same, because we have only extracted a function from React's useEffect Hook. Instead of using the data fetching logic directly in the side-effect, we made it available as a function for the entire application. The benefit: reusability. The data fetching can be used by other parts of the application by calling this new function.

However, we have used React's useCallback Hook to wrap the extracted function, so let's explore why it's needed here. React's useCallback Hook creates a memoized function every time its dependency array (E) changes. As a result, the useEffect hook runs again (C), because it depends on the new function (D):

Visualization

bash
1. change: searchTerm (cause: user interaction)
2. change: handleFetchStories
3. run: side-effect

If we didn't create a memoized function with React's useCallback Hook, a new handleFetchStories function would be created each time the App component re-renders, and would be executed in the useEffect hook to fetch data. The fetched data is then stored as state in the component. Because the state of the component changed, the component re-renders and creates a new handleFetchStories function. The side-effect would be triggered to fetch data, and we'd be stuck in an endless loop:

Visualization

bash
1. define: handleFetchStories
2. run: side-effect
3. update: state
4. re-render: component
5. re-define: handleFetchStories
6. run: side-effect
...

React's useCallback hook changes the function only when one of its values in the dependency array changes. That's when we want to trigger a re-fetch of the data, because the input field has new input and we want to see the new data displayed in our list.

By moving the data fetching function outside the React's useEffect Hook, it becomes reusable for other parts of the application. We won't use it just yet, but it is a good use case to understand the memoized functions in React. Now the useEffect hook runs implicitly when the searchTerm changes, because the handleFetchStories is re-defined each time the searchTerm changes. Since the useEffect hook depends on the handleFetchStories, the side-effect for data fetching runs again.

Exercises: