swr
swr copied to clipboard
Version 1 local storage provider does not work with version 2
Bug report
I am using the following provider that used to work with version 1.XX
interface Window {
addEventListener: any;
}
declare const window: Window;
import { State } from 'swr';
export function localStorageProvider() {
if (typeof window === 'undefined') {
return new Map([]) as Map<string, State<any, any>>;
}
// When initializing, we restore the data from `localStorage` into a map.
const map: Map<string, State<any, any>> = new Map(
JSON.parse(localStorage.getItem('app-cache') || '[]')
);
// Before unloading the app, we write back all the data into `localStorage`.
window.addEventListener('beforeunload', () => {
const appCache = JSON.stringify(Array.from(map.entries()));
localStorage.setItem('app-cache', appCache);
});
// We still use the map for write & read for performance.
return map;
}
Description / Observed Behavior
Using the above localStorage provider with version 2.0.0.beta-6 it throws the following error:
Error: Hydration failed because the initial UI does not match what was rendered on the server.
What kind of issues did you encounter with SWR?
Expected Behavior
Error should not be thrown provider should be excepted.
CodeSandBox Link
https://codesandbox.io/s/friendly-brattain-kpbfus?file=/pages/index.js
Additional Context
If I comment out the provider assignment in SWRConfig at the top level of the app, then everything works fine. Seems like it is not accepting the custom local storage provider above.
SWR version. 2.0.0.beta-6 next 12.2.5 react 18.2.0
Thanks for the suggestion @blackmann.
I tried creating a cache variable as you suggested, and tried 2 options:
1 - Moving it to the top of the main page function or 2 - Out of the function just after all the imports
In both scenarios the error still remains.
Hi, I also met this error and cannot use localStorageProvider because of hydration error. @blackmann 's solution also didn't work. Could there be any update for it?
I used:
- swr:
2.0.0-rc.0
- next:
12.3.1
- react:
18.2.0
The issue is still present on version 2.1.2, but I have found a workaround that can help.
To implement the solution, you need to define the localStorageProvider
function as shown in the documentation and load it after mount. You don't need to check for window
, as it will run on the client anyway. Here's the code:
function localStorageProvider() {
const appCache = localStorage.getItem('app-cache') || '[]';
const map = new Map(JSON.parse(appCache) as Iterable<[string, State<any, any>]>);
window.addEventListener('beforeunload', () => {
const appCache = JSON.stringify(Array.from(map.entries()));
localStorage.setItem('app-cache', appCache);
});
return map;
}
// Load the localStorageProvider function after mount
const cacheProvider = useRef<typeof localStorageProvider>();
useEffect(() => {
cacheProvider.current = localStorageProvider;
}, []);
<SWRConfig value={{ provider: cacheProvider.current }}>
<App/>
</SWRConfig>
I noticed an issue with the previous solution I provided. While it successfully implemented caching using local storage without hydration errors, it failed to load the cache on the first website load, which resulted in all SWR hooks without any data. To fix this, I suggest using a new useCacheProvider
hook that initializes only the cache, rather than the entire function, after mount.
Here's the new code:
// useCacheProvider hook
function useCacheProvider() {
const cache = useRef<Map<string, State<any, any>>>(new Map());
useEffect(() => {
const appCache = localStorage.getItem('app-cache');
if (appCache) {
const map = new Map(JSON.parse(appCache) as Iterable<[string, State<any, any>]>);
map.forEach((value, key) => cache.current.set(key, value));
}
const saveCache = () => {
const appCache = JSON.stringify(Array.from(cache.current.entries()));
localStorage.setItem('app-cache', appCache);
};
window.addEventListener('beforeunload', saveCache);
return () => window.removeEventListener('beforeunload', saveCache);
}, []);
return () => cache.current;
}
// use hook in SWRConfig
const provider = useCacheProvider();
<SWRConfig value={{ provider }}>
<App/>
</SWRConfig>