JavaScript Development Space

How to Cache List Data in React with react-query and localforage

Efficient data caching is essential for any React app handling large datasets or dealing with slow backend responses. In this guide, we’ll walk you through caching list data in React using react-query and localforage. These tools will help ensure quick access to data without repetitive API calls, even after page reloads.

Why Cache List Data?

In backend management systems, handling large or frequently-accessed data can slow down the user experience, especially when each reload requires multiple API requests. While React's useCallback and useMemo hooks can optimize performance by reducing re-renders, they lack data persistence across sessions.

A combination of react-query and localforage can be the answer. react-query manages server-side requests, caching, and synchronization, while localforage uses IndexedDB, WebSQL, and LocalStorage to store data locally. This approach ensures data persistence beyond the current session, reducing load times and improving UX.

Design Approach

Our caching strategy follows a simple plan:

  • On the initial load, data is fetched from the server and stored in localforage.
  • On subsequent loads, data is first accessed from local cache, reducing the number of server requests.
  • While the cached data is displayed immediately, an API call is triggered in the background to ensure that the data remains up-to-date.

This "local-first" approach significantly improves load times while ensuring data freshness.

Step-by-Step Implementation

Step 1: Configure localforage

Set up a localforage instance to store data by name. This enables data categorization and management across your app.

js
1 import localforage from "localforage";
2
3 export const localStore = localforage.createInstance({
4 user: "john-doe",
5 });

Step 2: Create useLocalforage Hook

useLocalforage is a custom hook to get and set data in localforage. This hook checks if data exists locally before making server requests.

js
1 import { useState, useCallback, useEffect } from "react";
2 import { useQueryClient } from "react-query";
3
4 const useLocalforage = (localKey) => {
5 const [localData, setLocalData] = useState();
6 const [localLoading, setLocalLoading] = useState(false);
7 const queryClient = useQueryClient();
8
9 const get = useCallback(async () => {
10 try {
11 return await localStore.getItem(localKey);
12 } catch (error) {
13 console.error("Error fetching from localforage:", error);
14 return null;
15 }
16 }, [localKey]);
17
18 const set = useCallback(async (data) => {
19 try {
20 return await localStore.setItem(localKey, data);
21 } catch (error) {
22 console.error("Error setting data in localforage:", error);
23 return null;
24 }
25 }, [localKey]);
26
27 useEffect(() => {
28 (async () => {
29 setLocalLoading(true);
30 const data = await get();
31 if (data) setLocalData(data);
32 setLocalLoading(false);
33 })();
34 }, [localKey]);
35
36 return { localData, localLoading, get, set };
37 };

Step 3: Implement useLocalQuery Hook

The useLocalQuery hook combines react-query with localforage to manage caching and persistence. Data is retrieved from cache on subsequent loads, with updates triggered in the background for real-time accuracy.

js
1 import { useQuery } from "react-query";
2 import { useLocalforage } from "./useLocalforage";
3
4 const useLocalQuery = ({ queryKey, queryFn, onSuccess, onError }) => {
5 const { localData, localLoading, get, set } = useLocalforage(queryKey);
6 const queryClient = useQueryClient();
7
8 const { data: queryData, isLoading, refetch } = useQuery({
9 queryKey,
10 queryFn,
11 onSuccess: (data) => {
12 set(data); // Update localforage
13 onSuccess && onSuccess(data);
14 },
15 onError,
16 });
17
18 const data = localData || queryData;
19
20 return { data, isLoading: localLoading || isLoading, refetch };
21 };

Example Component Usage

Here’s an example of how to use useLocalQuery within a component to fetch a list of items and display them from cache.

js
1 import axios from "axios";
2 import useLocalQuery from "./useLocalQuery";
3
4 const fetchListData = async () => {
5 const { data } = await axios.get("/api/our-data");
6 return data;
7 };
8
9 const ListComponent = () => {
10 const { data, isLoading, refetch } = useLocalQuery({
11 queryKey: "list-data",
12 queryFn: fetchListData,
13 onSuccess: (data) => console.log("Data fetched successfully:", data),
14 });
15
16 if (isLoading) return <p>Loading...</p>;
17
18 return (
19 <div>
20 <ul>
21 {data.map((item) => (
22 <li key={item.id}>{item.name}</li>
23 ))}
24 </ul>
25 <button onClick={refetch}>Refresh Data</button>
26 </div>
27 );
28 };

Conclusion

By implementing react-query and localforage in your React app, you can cache list data efficiently, making your app faster and more responsive. This setup minimizes repetitive requests, and improves UX by ensuring the data is available even on page reloads. Try it in your projects, and start building smarter, faster applications!

JavaScript Development Space

© 2024 JavaScript Development Space - Master JS and NodeJS. All rights reserved.