React Hooks and a Cache from Scratch

Nick Bell
5 min readFeb 14, 2019

--

React 16.8 — “The One With Hooks” — was recently released, cementing the Hooks API into the react core. Hooks have taken the React ecosphere by storm, and there are already dozens of custom hooks published to Github for you to use. In this article, we explore building a simple client-side cache from scratch, and how implementing this cache with React Hooks has a “gotcha” that you need to look out for.

A common issue in single page application (SPA) development is that of dynamically updating data based on API results. An example of this is a search bar, that when input is received, dynamically updates data on the page. For instance, the google home page will provide “suggestions” when you type the first few characters into their search box:

This is an example of “live updating” (in this case, “typeahead”).

In React, we can pretty easily achieve this using the onChange handler of an input. This will update the state of the component, and we can introduce API calls which return results, and we can update our component with the results (ancillary components extracted):

And see it in action:

Now, as the search query expands, new API calls will be necessary to filter the results set. This is nearly unavoidable, as a query that starts with “Ind” could expand to “Independent Films” or “Index Funds” or “Indiana Hoosiers”, and it would be difficult to predict where the query would go without previous information (which Google has… a LOT of).

This implementation simply calls the API with a query every time the input changes. This has two drawbacks:

  1. Every character change induces an API call, even mistakes and backspaces
  2. The initial results set is very large, and costly for bandwidth.

Next, if the user mis-spells a word as they’re typing (and since the topic of fuzzy matching is outside the scope of this article), they’re likely going to backspace their input until they correctly spell their query. As they backspace, this implementation re-triggers the API call, a potentially expensive operation that could slow down the user flow. We’ve all seen this, and it’s really annoying, so we’re going to introduce a cache into the flow.

Here is an example of a simple cached search structure:

This cache will keep a record of the queries it has received, and the results from those queries. Whenever a query comes through that is present in the cache, the module simply returns the value from the cache, rather than calling the API again to get (in most scenarios) the exact same response.

This implementation also only sends an API call when there are 3 or more characters in the query, avoiding huge payloads on short queries.

Utilizing this in our search component allows us to avoid these costly API calls when the query matches one we’ve seen before:

Now, we can see the cached search component in action (with stats):

Up until now we’ve used traditional class-based components. These are *so* 2016, and it’s now 2019, and Hooks are in React and Functional Components are the (somewhat older but still) *new thing*, so let’s refactor our form to use them:

Awesome! It is much cleaner, uses setState , and is much easier to reason out what the component is performing. Now, we can test it:

Uh oh, what happened? The search works… but not the caching.

Let’s think about it for a minute. When a functional component needs to update, it runs the entire function again. In our first try at using Hooks, we declared:

const cachedResults = new CachedResults(search, setResults);

Every time the component needs to render, it’s going to create a new instance of CachedResults ! This defeats the purpose of the cache, since it needs to persist its results set across render cycles. Luckily, (and I should copyright this phrase…) *There’s a Hook for That!*

Enter useMemo:

useMemo is a function that takes two parameters:

  1. A function that calculates the value to return.
  2. A list of props or state values that trigger a recalculation on change.

In our case, we only want the cachedSearch value to be created on component load, so we pass an empty array as the second argument. Let’s see what that does to our search box:

Ah, there we go. Now our results are cached *and* we’re using Hooks.

Hooks are a powerful construct that allow us to write React in a functional style, but thinking in an FP-oriented mindset requires some adjustments to the way we code.

I hope this article was enlightening! This CachedResults pattern could be extended to wrap our API calls in additional functionality, including debouncing and throttling, as well.

As mentioned above, here is the full sandbox with all four examples we went over today (simple, cached, bad hooks, and good hooks):

Want to ask questions or chat more? You can find me on Twitter or Github.

Notes:

https://mockaroo.com/ was used to generate mock data.

--

--