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.
1 import localforage from "localforage";23 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.
1 import { useState, useCallback, useEffect } from "react";2 import { useQueryClient } from "react-query";34 const useLocalforage = (localKey) => {5 const [localData, setLocalData] = useState();6 const [localLoading, setLocalLoading] = useState(false);7 const queryClient = useQueryClient();89 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]);1718 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]);2627 useEffect(() => {28 (async () => {29 setLocalLoading(true);30 const data = await get();31 if (data) setLocalData(data);32 setLocalLoading(false);33 })();34 }, [localKey]);3536 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.
1 import { useQuery } from "react-query";2 import { useLocalforage } from "./useLocalforage";34 const useLocalQuery = ({ queryKey, queryFn, onSuccess, onError }) => {5 const { localData, localLoading, get, set } = useLocalforage(queryKey);6 const queryClient = useQueryClient();78 const { data: queryData, isLoading, refetch } = useQuery({9 queryKey,10 queryFn,11 onSuccess: (data) => {12 set(data); // Update localforage13 onSuccess && onSuccess(data);14 },15 onError,16 });1718 const data = localData || queryData;1920 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.
1 import axios from "axios";2 import useLocalQuery from "./useLocalQuery";34 const fetchListData = async () => {5 const { data } = await axios.get("/api/our-data");6 return data;7 };89 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 });1516 if (isLoading) return <p>Loading...</p>;1718 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!