Skip to content

Instantly share code, notes, and snippets.

@ali-sabry
Created April 27, 2023 15:36
Show Gist options
  • Save ali-sabry/9a9349bce3908911be2b41a27cc47b2b to your computer and use it in GitHub Desktop.
Save ali-sabry/9a9349bce3908911be2b41a27cc47b2b to your computer and use it in GitHub Desktop.
useOfflineFirst custom hook: this hook allows you to build offline-first apps which means apps that can work with or without an Internet connection, to provide a fast and reliable user experience and they are especially useful for scenarios where the network is unreliable or unavailable.
//=== Custom Hook
import React, { useState, useEffect } from "react";
function useOfflineFirst(url) {
const [isOnline, setIsOnline] = useState(true);
const [data, setData] = useState(null);
const updateNetworkStatus = () => {
if (typeof navigator !== "undefined" && typeof navigator.onLine === "boolean") setIsOnline(navigator.onLine);
};
const openDatabase = () => {
return new Promise((resolve, reject) => {
if (typeof indexedDB !== "undefined") {
const request = indexedDB.open("offline-first", 1);
request.onsuccess = (e) => resolve(e.target.result);
request.onerror = (e) => reject(e.target.error);
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore("data", { keyPath: "url" });
};
} else reject(new Error("IndexedDB is not supported"));
});
};
const saveData = (url, data) => {
openDatabase()
.then((db) => {
const tx = db.transaction("data", "readwrite");
const store = tx.objectStore("data");
store.put({ url, data });
}).catch((error) => console.error(error));
};
const loadData = (url) => {
openDatabase()
.then((db) => {
const tx = db.transaction("data", "readonly");
const store = tx.objectStore("data");
const request = store.get(url);
request.onsuccess = (e) => {
if (e.target.result) setData(e.target.result.data);
};
request.onerror = (e) => console.error(e.target.error);
}).catch((error) => console.error(error));
};
const fetchData = (url) => {
fetch(url)
.then((response) => {
if (response.ok) return response.json();
else throw new Error(response.statusText);
}).then((data) => {
setData(data);
saveData(url, data);
}).catch((error) => console.error(error));
};
useEffect(() => {
updateNetworkStatus();
window.addEventListener("online", updateNetworkStatus);
window.addEventListener("offline", updateNetworkStatus);
return () => {
window.removeEventListener("online", updateNetworkStatus);
window.removeEventListener("offline", updateNetworkStatus);
};
}, []); // Run only once on mount and unmount
useEffect(() => {
if (url) {
if (isOnline) return fetchData(url)
else loadData(url);
}
}, [isOnline, url]); // Run whenever the network status or url changes
return { isOnline, data };
}
//==== Usage
const App = ()=> {
const { isOnline, data } = useOfflineFirst("https://jsonplaceholder.typicode.com/users");
return (
<div className="App">
<h1>Offline-First App</h1>
{isOnline ? ( // Render a message based on the network status
<p>You are online. Data is fetched from the server.</p>
) : (
<p>You are offline. Data is loaded from IndexedDB.</p>
)}
{data ? ( // Render a list of users if data is available
<ul >
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
) : (
<p>Loading...</p>
)}
</div>
);
}
export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment