Gathering detailed insights and metrics for swr-global-state
Gathering detailed insights and metrics for swr-global-state
Gathering detailed insights and metrics for swr-global-state
Gathering detailed insights and metrics for swr-global-state
@zyda/swr-internal-state
Use SWR to manage app's internal state
x-state-manager
A lightweight state manager built on top of SWR for efficient state management with caching, optimistic updates, and modular architecture. It allows easy global and local state management, automatic data re-fetching on focus, and supports TypeScript
♻️ Zero-setup & simple global state management for React Components. It's similiar `useState` hooks like we use usual!
npm install swr-global-state
Typescript
Module System
Min. Node Version
Node Version
NPM Version
TypeScript (93.43%)
JavaScript (3.66%)
CSS (2.91%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
43 Stars
230 Commits
1 Forks
1 Watchers
1 Branches
1 Contributors
Updated on Jul 08, 2025
Latest Version
2.3.2
Package Id
swr-global-state@2.3.2
Unpacked Size
39.99 kB
Size
12.71 kB
File Count
17
NPM Version
10.8.2
Node Version
18.20.8
Published on
Jul 08, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
3
Zero-setup & simple global state management for React Components based on SWR helpers. With this library, you can focus on your awesome React Project and not waste another afternoon on the setup & configuring your global state. 🌄
1npm i swr swr-global-state
1yarn add swr swr-global-state
Create a new file for your global state on your root directory. And then, use createStore
. Example: stores/counter.js
1// file: stores/counter.js 2 3import { createStore } from "swr-global-state"; 4 5const useCounter = createStore({ 6 key: "@app/counter", // (Required) state key with unique string 7 initial: 0 // <- (Required) initial state 8}); 9 10export default useCounter;
You just import stores that you have created into your any components, then use it like you use useState
as usual.
1// file: components/SetCountComponent.js 2 3import useCounter from "stores/counter"; 4 5function SetCountComponent() { 6 const [, setCount] = useCounter(); // <- `[, ]` skipping first index of the array. 7 return ( 8 <div> 9 <button onClick={() => setCount(prev => prev - 1)}> 10 (-) Decrease Count 11 </button> 12 13 <button onClick={() => setCount(prev => prev + 1)}> 14 (+) Increase Count 15 </button> 16 </div> 17 ); 18} 19 20export default SetCountComponent;
1// file: components/GetCountComponent.js 2 3import useCounter from "stores/counter"; 4 5function GetCountComponent() { 6 const [count] = useCounter(); 7 return ( 8 <div> 9 <p>Current Count: {count}</p> 10 </div> 11 ); 12} 13 14export default GetCountComponent;
Optionally, you can define persistor
object to create custom persistor to hold your state even user has closing app/browser, and re-opened it.
In this example, we use localStorage
to hold our state.
1// file: stores/counter.js
2
3import { createStore } from "swr-global-state";
4
5const useCounter = createStore({
6 key: "@app/counter",
7 initial: 0,
8 persistor: { // <- Optional, use this if you want hold the state
9 onSet: (key, data) => {
10 window.localStorage.setItem(String(key), data);
11 },
12 onGet: (key) => {
13 const cachedData = window.localStorage.getItem(String(key));
14 return Number(cachedData);
15 }
16 }
17});
18
19export default useCounter;
We can create reusable persistor
to re-use in every stores that we have created. Example:
1// file: persistors/local-storage.ts 2 3import type { StatePersistor, StateKey } from "swr-global-state"; 4 5const withLocalStoragePersistor = <T = any>(): StatePersistor<T> => ({ 6 onSet(key: StateKey, data: T) { 7 const stringifyData = JSON.stringify(data); 8 window.localStorage.setItem(String(key), stringifyData); 9 }, 10 onGet(key: StateKey) { 11 const cachedData = window.localStorage.getItem(String(key)) ?? "null"; 12 try { 13 return JSON.parse(cachedData); 14 } catch { 15 return cachedData; 16 } 17 } 18}); 19 20export default withLocalStoragePersistor;
Now, we can use that withLocalStoragePersistor
in that like this:
1// file: stores/counter.ts 2 3import { createStore } from "swr-global-state"; 4import withLocalStoragePersistor from "persistors/local-storage"; 5 6const useCounter = createStore<number>({ 7 key: "@app/counter", 8 initial: 0, 9 persistor: withLocalStoragePersistor() 10}); 11 12export default useCounter;
1// file: stores/theme.ts 2 3import { createStore } from "swr-global-state"; 4import withLocalStoragePersistor from "persistors/local-storage"; 5 6const useTheme = createStore<string>({ 7 key: "@app/theme", 8 initial: "light", 9 persistor: withLocalStoragePersistor() 10}); 11 12export default useTheme;
Just use async function or promise as usual in onSet
and onGet
.
1// file: stores/counter.js 2 3import AsyncStorage from '@react-native-async-storage/async-storage'; 4import { createStore } from "swr-global-state"; 5 6const useCounter = createStore({ 7 key: "@app/counter", 8 initial: 0, 9 persistor: { 10 async onSet(key, data) { 11 try { 12 await AsyncStorage.setItem(String(key), data); 13 } catch (err) { 14 // handle saving error, default throw an error 15 throw new Error(err); 16 } 17 }, 18 async onGet(key) { 19 try { 20 const value = await AsyncStorage.getItem(String(key)); 21 return Number(value); 22 } catch (err) { 23 // handle error reading value 24 throw new Error(err); 25 } 26 } 27 } 28}); 29 30export default useCounter;
Rate limiting helps optimize performance by controlling how frequently state updates are persisted. This is especially useful for rapid user interactions or frequent state changes.
Debounce delays the execution until after a specified time has passed since the last call. Perfect for scenarios like search inputs or rapid counter increments.
1// file: stores/async-counter.ts 2 3import { createStore } from 'swr-global-state'; 4import asyncStoragePersistor from '../persistors/async-storage'; 5 6/** 7 * Counter store with async storage persistence and debounce rate limiting 8 * Demonstrates async operations with loading states and optimized persistence 9 */ 10const useAsyncCounter = createStore<number>({ 11 key: '@app/async-counter', 12 initial: 0, 13 persistor: asyncStoragePersistor, 14 rateLimit: { 15 type: 'debounce', 16 delay: 2000 // Wait 2 seconds after last change before persisting 17 } 18}); 19 20export default useAsyncCounter;
Throttle ensures that the function is called at most once per specified interval. Ideal for preventing excessive API calls during rapid typing.
1// file: stores/search-history.ts 2 3import { createStore } from 'swr-global-state'; 4import localStoragePersistor from '../persistors/local-storage'; 5 6interface SearchHistory { 7 queries: string[]; 8 lastSearch: string; 9 searchCount: number; 10} 11 12const defaultSearchHistory: SearchHistory = { 13 queries: [], 14 lastSearch: '', 15 searchCount: 0 16}; 17 18/** 19 * Search history store with throttle rate limiting 20 * Prevents excessive API calls during rapid typing 21 */ 22const useSearchHistory = createStore<SearchHistory>({ 23 key: '@app/search-history', 24 initial: defaultSearchHistory, 25 persistor: localStoragePersistor, 26 rateLimit: { 27 type: 'throttle', 28 delay: 1000 // Allow persistence at most once per second 29 } 30}); 31 32export default useSearchHistory;
For advanced use cases, you can implement custom rate limiting logic that combines multiple strategies.
1// file: stores/count-persisted.ts 2 3import { createStore } from 'swr-global-state'; 4import localStoragePersistor from '../persistors/local-storage'; 5 6/** 7 * Custom rate limiting function that combines debounce and throttle 8 * Provides immediate feedback but limits actual persistence calls 9 */ 10const customCounterRateLimit = <T>(func: (key: any, data: T) => Promise<void>, delay: number) => { 11 let lastCall = 0; 12 let timeoutId: NodeJS.Timeout; 13 14 return (key: any, data: T) => { 15 const now = Date.now(); 16 17 // Throttle: if too fast, skip 18 if (now - lastCall < delay / 3) { 19 return; 20 } 21 22 // Debounce: delay execution for batch updates 23 clearTimeout(timeoutId); 24 timeoutId = setTimeout(() => { 25 lastCall = Date.now(); 26 func(key, data); 27 }, delay); 28 }; 29}; 30 31const usePersistedCounter = createStore<number>({ 32 key: '@app/persisted-counter', 33 initial: 0, 34 persistor: { 35 onSet: customCounterRateLimit(localStoragePersistor.onSet, 1500), 36 onGet: localStoragePersistor.onGet 37 } 38}); 39 40export default usePersistedCounter;
Demonstrates how to handle async operations with loading states and error handling.
1// file: components/AsyncCounter.tsx 2 3import { useState } from 'react'; 4import useAsyncCounter from '../stores/async-counter'; 5 6export default function AsyncCounter() { 7 const [counter, setCounter, { isLoading, error, isPersisting }] = useAsyncCounter(); 8 const [isUpdating, setIsUpdating] = useState(false); 9 10 const handleIncrement = async() => { 11 setIsUpdating(true); 12 await setCounter(prev => prev + 1); 13 setIsUpdating(false); 14 }; 15 16 const handleDecrement = async() => { 17 setIsUpdating(true); 18 await setCounter(prev => prev - 1); 19 setIsUpdating(false); 20 }; 21 22 const handleReset = async() => { 23 setIsUpdating(true); 24 await setCounter(50); 25 setIsUpdating(false); 26 }; 27 28 if (isLoading) { 29 return <div>Loading counter...</div>; 30 } 31 32 if (error) { 33 return <div>Error loading counter: {error.message}</div>; 34 } 35 36 return ( 37 <div style={{ padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}> 38 <h3>Async Counter with Debounce (2s)</h3> 39 <div style={{ fontSize: '24px', margin: '10px 0' }}> 40 Count: {counter} 41 </div> 42 43 <div style={{ display: 'flex', gap: '10px', marginBottom: '10px' }}> 44 <button 45 onClick={handleDecrement} 46 disabled={isUpdating || isPersisting} 47 > 48 {isUpdating ? 'Updating...' : 'Decrement'} 49 </button> 50 <button 51 onClick={handleIncrement} 52 disabled={isUpdating || isPersisting} 53 > 54 {isUpdating ? 'Updating...' : 'Increment'} 55 </button> 56 <button 57 onClick={handleReset} 58 disabled={isUpdating || isPersisting} 59 > 60 {isUpdating ? 'Updating...' : 'Reset to 50'} 61 </button> 62 </div> 63 64 {isPersisting && ( 65 <div style={{ color: 'orange', fontSize: '12px' }}> 66 💾 Saving to storage... 67 </div> 68 )} 69 70 <div style={{ fontSize: '12px', color: '#666', marginTop: '10px' }}> 71 💡 Changes are debounced and saved 2 seconds after the last update 72 </div> 73 </div> 74 ); 75}
Shows complex state management with nested objects and multiple async operations.
1// file: components/AsyncProfile.tsx 2 3import { useState, useEffect } from 'react'; 4import useAsyncProfile from '../stores/async-profile'; 5 6function AsyncProfile() { 7 const [profile, setProfile, { isLoading, error }] = useAsyncProfile(); 8 const [isUpdating, setIsUpdating] = useState(false); 9 const [formData, setFormData] = useState({ 10 name: profile.name, 11 email: profile.email 12 }); 13 14 // Sync formData with profile when profile changes 15 useEffect(() => { 16 setFormData({ 17 name: profile.name, 18 email: profile.email 19 }); 20 }, [profile.name, profile.email]); 21 22 const handleUpdateProfile = async() => { 23 setIsUpdating(true); 24 try { 25 await setProfile(prev => ({ 26 ...prev, 27 name: formData.name, 28 email: formData.email, 29 lastUpdated: new Date().toISOString() 30 })); 31 } catch (err) { 32 console.error('Failed to update profile:', err); 33 } finally { 34 setIsUpdating(false); 35 } 36 }; 37 38 const handleToggleTheme = async() => { 39 setIsUpdating(true); 40 try { 41 await setProfile(prev => ({ 42 ...prev, 43 preferences: { 44 ...prev.preferences, 45 theme: prev.preferences.theme === 'light' ? 'dark' : 'light' 46 }, 47 lastUpdated: new Date().toISOString() 48 })); 49 } catch (err) { 50 console.error('Failed to toggle theme:', err); 51 } finally { 52 setIsUpdating(false); 53 } 54 }; 55 56 if (isLoading) { 57 return <div>Loading profile...</div>; 58 } 59 60 if (error) { 61 return <div>Error loading profile: {error.message}</div>; 62 } 63 64 return ( 65 <div style={{ padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}> 66 <h3>Async Profile Management</h3> 67 68 <div style={{ marginBottom: '15px' }}> 69 <label> 70 Name: 71 <input 72 type="text" 73 value={formData.name} 74 onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))} 75 style={{ marginLeft: '10px', padding: '5px' }} 76 /> 77 </label> 78 </div> 79 80 <div style={{ marginBottom: '15px' }}> 81 <label> 82 Email: 83 <input 84 type="email" 85 value={formData.email} 86 onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))} 87 style={{ marginLeft: '10px', padding: '5px' }} 88 /> 89 </label> 90 </div> 91 92 <div style={{ display: 'flex', gap: '10px', marginBottom: '15px' }}> 93 <button 94 onClick={handleUpdateProfile} 95 disabled={isUpdating} 96 > 97 {isUpdating ? 'Updating...' : 'Update Profile'} 98 </button> 99 <button 100 onClick={handleToggleTheme} 101 disabled={isUpdating} 102 > 103 {isUpdating ? 'Updating...' : `Switch to ${profile.preferences.theme === 'light' ? 'Dark' : 'Light'}`} 104 </button> 105 </div> 106 107 <div style={{ fontSize: '12px', color: '#666' }}> 108 <div>Current Theme: {profile.preferences.theme}</div> 109 <div>Last Updated: {new Date(profile.lastUpdated).toLocaleString()}</div> 110 </div> 111 </div> 112 ); 113} 114 115export default AsyncProfile;
Can't find your cases in this documentation examples? You can create custom hooks by yourself. Here is complex example you can refer the pattern to create another custom hooks cases.
1// file: stores/account.js 2... 3import useStore from "swr-global-state"; 4 5const KEY = "@app/account"; 6 7function useAccount() { 8 const [loading, setLoading] = useStore({ 9 key: `${KEY}-loading`, 10 initial: true 11 }); 12 const [account, setAccount, swrDefaultResponse] = useStore( 13 { 14 key: KEY, 15 initial: null, 16 persistor: { 17 onSet: (key, accountData) => { 18 window.localStorage.setItem(String(key), JSON.stringify(accountData)); 19 }, 20 onGet: async(key) => { 21 if (window.navigator.onLine) { 22 const remoteAccount = await fetch('/api/account'); 23 return remoteAccount.json(); 24 } 25 const cachedAccount = window.localStorage.getItem(String(key)); 26 setLoading(false); 27 return JSON.parse(cachedAccount); 28 } 29 } 30 }, 31 { 32 /** 33 * set another SWR config here 34 * @see https://swr.vercel.app/docs/options#options 35 * @default on `swr-global-state`: 36 * revalidateOnFocus: false 37 * revalidateOnReconnect: false 38 * refreshWhenHidden: false 39 * refreshWhenOffline: false 40 */ 41 revalidateOnFocus: true, 42 revalidateOnReconnect: true 43 } 44 ); 45 46 /** 47 * Destructuring response from SWR Default response 48 * @see https://swr.vercel.app/docs/options#return-values 49 */ 50 const { mutate, error } = swrDefaultResponse; 51 52 const destroyAccount = async () => { 53 setLoading(true); 54 await fetch('/api/account/logout'); 55 window.localStorage.removeItem(KEY); 56 // use default `mutate` from SWR to avoid `onSet` callback in `persistor` 57 mutate(null); 58 setLoading(false); 59 }; 60 61 const updateAccount = async (newAccountData) => { 62 setLoading(true); 63 await fetch('/api/account', { 64 method: 'POST', 65 body: JSON.stringify(newAccountData) 66 ... 67 }) 68 setAccount(newAccountData); 69 setLoading(false); 70 }; 71 72 // your very custom mutator/dispatcher 73 74 return { 75 loading, 76 error, 77 account, 78 updateAccount, 79 destroyAccount 80 }; 81} 82 83export default useAccount;
Then, use that custom hooks in your component as usual.
1// file: App.js 2... 3import useAccount from "stores/account"; 4 5function App() { 6 const { 7 account, 8 updateAccount, 9 destroyAccount, 10 loading, 11 error 12 } = useAccount(); 13 14 const onLogout = async () => { 15 await destroyAccount() 16 // your very logic 17 } 18 19 if (loading) { 20 return <div>Loading...</div>; 21 } 22 23 if (error) { 24 return <div>An Error occured</div>; 25 } 26 27 return ( 28 <div> 29 <p>Account Detail: {JSON.stringify(account)}</p> 30 <button onClick={onLogout}>Logout</button> 31 {/* your very component to update account */} 32 </div> 33 ); 34} 35 36export default App;
You can see live demo here
Featured Demos:
For AI assistants and Large Language Models (LLMs) working with this library, we provide comprehensive usage rules and patterns to help understand and implement swr-global-state
effectively.
A detailed guide for AI assistants is available that covers:
📖 View the complete AI usage rules: llm-rules.md
This guide is specifically designed to help AI assistants:
useState
as usual.Redux
or Context API
alternative.SWR
, but you have no idea how to manage synchronous global state with SWR
on client-side.Redux
or Context API
, but you are overwhelmed with their flow.Redux
, how about asynchronous state management like redux-saga
, redux-thunk
, or redux-promise
?At this point, swr-global-state
is based and depends on SWR. After version >2
or later, swr-global-state
now can handle async state too. Just wraps your very async state logic into a function like in Custom Hooks or Asynchronous Persistor.
Additionally, swr-global-state
provides built-in rate limiting strategies (debounce, throttle, custom) that help optimize performance for frequent state updates, which is especially useful for async operations.
So, you basically don't need to use Redux
or Context API
anymore. Alternatively, you can choose TanStack Query or default SWR itself.
Since SWR itself supports React Native, of course swr-global-state
supports it too. This example is using Async Storage
in React Native.
Things to note, you must install swr-global-state
version >2
or later, because it has customizable persistor
. So, you can customize the persistor
with React Native Async Storage
.
Under version <2
, swr-global-state
still use localStorage
and we can't customize it. So, it doesn't support React Native.
version
in package.json
is changed to newest version. Then run npm install
for synchronize it to package-lock.json
main
, you can publish the packages by creating new Relase here: https://github.com/gadingnst/swr-global-state/releases/newtag
, make sure the tag
name is same as the version
in package.json
.Publish Release
button, then wait the package to be published.swr-global-state
is freely distributable under the terms of the MIT license.
Feel free to open issues if you found any feedback or issues on swr-global-state
. And feel free if you want to contribute too! 😄
Built with ❤️ by Sutan Gading Fadhillah Nasution on 2022
No vulnerabilities found.
No security vulnerabilities found.