Cache Strategies to Provide a Better UX in your React Applications

In case of improving a website's performance we need to reduce the use of our resources. Integrating a caching strategy is one of the best ways which can significantly help us on that case.

Depending on your specific requirements, there are three primary approaches to integrating a caching solution with your React application.

These requirements are related to several factors, including the duration for which we want to store the content, the frequency at which the cached content updates, the dependability of server requests, and the type of application being developed, which may prioritize online or offline interactions.

Server Response & Caching Policy

Notes: In this article, we’ll be working with the Browser’s Local Storage API

Table of Contents

  1. Fallback Caching
  2. Local-first Caching
  3. Versioned Cache
  4. Conclusion

Fallback Caching

This strategy may be the simplest, yet effective caching approach. By storing a previous version of the content we are attempting to retrieve, it protects us from failed requests.

While it may not significantly enhance performance, its true advantage lies in serving as a safeguard against unexpected scenarios resulting from server request failures.

Here is an example in react:

const [todos, setTodos] = useState([]);
useEffect(() => {
  const fetchTodos = async () => {
    try {
      const { data } = await axios.get('https://jsonplaceholder.typicode.com/todos');
      setTodos(data);
      localStorage.setItem("todos", data);
    } catch (err) {
      setTodos(localStorage.getItem("todos") || []);
    }
  };
  fetchTodos();
}, []);

Local-first Caching

This caching strategy is particularly suitable for situations where we want to cache content that is not likely to change frequently, such as a navigation links list, a user's profile information (e.g., profile picture URL, bio, birth date, username), an archive, and so on.
The caching strategy involves checking the cache before making a request to retrieve data, allowing us to avoid requesting data on subsequent requests after the initial one. This approach is particularly effective when caching content on frequently visited pages.

Here is an example code in react:

const [todos, setTodos] = useState([]);
useEffect(() => {
  const availableTodos = localStorage.getItem("todos");
  const fetchTodos = async () => {
    try {
      const { data } = await axios.get('https://jsonplaceholder.typicode.com/todos');
      setTodos(data);
      localStorage.setItem("todos", data);
    } catch (err) {
      // Handle Error
    }
  };
  if (!availableTodos) {
    fetchTodos();
  }
}, []);

Versioned Cache

A more advanced caching technique involves versioning the cache by assigning a version label to the cached content, along with the content itself, within a cache storage entry.

Versioning is a controlled process that allows us to trigger side effects on a locally computed version to update the cache whenever necessary. This is accomplished in a similar manner to the code snippet provided below.

const [todos, setTodos] = useState([]);
useEffect(() => {
  // Cache Entry
  const cachedEntry = localStorage.getItem('todos');
  const cachedTodos = cachedEntry.data;
  const version = cachedEntry.version;
  // This will be computed according to your use-case
  const computedVersion = 2;
  const fetchTodos = async () => {
    try {
      const { data } = await axios.get('https://jsonplaceholder.typicode.com/todos');
      setTodos(data);
      localStorage.setItem("todos", {
        version: computedVersion,
        data: data,
      });
    } catch (err) {
      // Handle Error
    }
  };
  if (!version || version < computedVersion) {
    fetchTodos();
  } else {
    setTodos(cachedTodos);
  }
}, []);

As demonstrated in this example, each cached entry is assigned a version label that tracks the current cache version to an externally computed value, acting as a control switch. This ensures smooth control of the caching process, allowing for cache updates as necessary.

However, the downside of this strategy is increased complexity in managing the caching process, as it requires careful orchestration of side effects.

Conclusion

Integrating caching solutions in React applications has several benefits, including improved performance and user experience by reducing load times, as well as reduced resource usage by reducing the number of requests sent to the server, resulting in less overall pressure on the back-end architecture.

I hope that you found this article enjoyable and informative. Thanks for reading.