Skip to content

Commit 2cd4390

Browse files
authored
Merge pull request #108 from gadingnst/v2.3.2
V2.3.2
2 parents 03be348 + b10a798 commit 2cd4390

File tree

11 files changed

+190
-40
lines changed

11 files changed

+190
-40
lines changed

apps/swr-global-state-demo/components/AsyncProfile.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-console */
12
import { useState, useEffect } from 'react';
23
import useAsyncProfile from '../states/stores/async-profile';
34

apps/swr-global-state-demo/pages/about.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ const About = () => {
5454
<Link className="App-link" href="/async-demo">
5555
Try Async Storage Demo
5656
</Link>
57+
<Link className="App-link" href="/test-input">
58+
Test Input
59+
</Link>
5760
</div>
5861
</header>
5962
</div>

apps/swr-global-state-demo/pages/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ const Home = () => {
2828
<Link className="App-link" href="/async-demo">
2929
Try Async Storage Demo
3030
</Link>
31+
<Link className="App-link" href="/test-input">
32+
Test Input
33+
</Link>
3134
</div>
3235
</header>
3336
</div>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useStore } from 'swr-global-state';
2+
3+
import Link from 'next/link';
4+
5+
const TestInputPage = () => {
6+
const [prompt, setPrompt] = useStore({
7+
key: 'agi-creator-gen-image-prompt',
8+
initial: ''
9+
});
10+
11+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
12+
setPrompt(e.target.value);
13+
};
14+
15+
return (
16+
<div className="App">
17+
<header className="App-header">
18+
<p style={{ color: 'aquamarine' }}>
19+
This is Test Input Page.
20+
</p>
21+
<div style={{ marginBottom: '20px', width: '80%' }}>
22+
<label htmlFor="prompt-input" style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', fontSize: 14 }}>
23+
Image Prompt:
24+
</label>
25+
<textarea
26+
id="prompt-input"
27+
value={prompt}
28+
onChange={handleChange}
29+
placeholder="Type your image prompt here..."
30+
className="App-input"
31+
rows={4}
32+
/>
33+
</div>
34+
35+
<div style={{ marginBottom: '20px', fontSize: 14 }}>
36+
<p><strong>Current Value:</strong> {prompt || '(empty)'}</p>
37+
<p><strong>Length:</strong> {prompt.length} characters</p>
38+
</div>
39+
40+
<div style={{ display: 'flex', gap: '10px' }}>
41+
<button
42+
onClick={() => setPrompt('')}
43+
className="App-button"
44+
>
45+
Clear
46+
</button>
47+
<button
48+
onClick={() => setPrompt('A beautiful sunset over mountains')}
49+
className="App-button"
50+
>
51+
Sample Text
52+
</button>
53+
</div>
54+
<div style={{ display: 'flex', gap: '20px', marginTop: '20px' }}>
55+
<Link className="App-link" href="/">
56+
Go To Home
57+
</Link>
58+
</div>
59+
</header>
60+
</div>
61+
);
62+
};
63+
64+
export default TestInputPage;

package-lock.json

Lines changed: 81 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "swr-global-state",
3-
"version": "2.3.1",
3+
"version": "2.3.2",
44
"author": "Sutan Gading Fadhillah Nasution <contact@gading.dev>",
55
"description": "Zero-setup & simple global state management for React Components",
66
"license": "MIT",

packages/swr-global-state/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "swr-global-state",
33
"author": "Sutan Gading Fadhillah Nasution <contact@gading.dev>",
44
"description": "Zero-setup & simple global state management for React Components",
5-
"version": "2.3.1",
5+
"version": "0.0.0",
66
"license": "MIT",
77
"main": "dist/index.js",
88
"typings": "dist/index.d.ts",

packages/swr-global-state/src/lib/useStore.ts

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -127,41 +127,41 @@ export function useStore<T, E = any>(
127127

128128
const { data: state, mutate, error, isLoading } = swrResponse;
129129

130-
const setState: StateMutator<T> = useCallback(async(data: T|StateMutatorCallback<T>, opts?: boolean|MutatorOptions<T>) => {
131-
return mutate(async(currentData: T | undefined) => {
132-
const actualCurrentData = currentData ?? cache.get(cacheKey)?.data ?? state;
130+
const setState: StateMutator<T> = useCallback(
131+
(data: T | StateMutatorCallback<T>, opts?: boolean | MutatorOptions<T>) => {
132+
const mutator = (currentData: T | undefined): T => {
133+
const resolvedCurrentData = currentData ?? state;
134+
135+
const newData = typeof data === 'function'
136+
? (data as (currentState: T) => T)(resolvedCurrentData as T)
137+
: data;
138+
139+
if (JSON.stringify(newData) === JSON.stringify(resolvedCurrentData)) {
140+
return resolvedCurrentData as T;
141+
}
133142

134-
const setPersist = async(newState: T) => {
135143
if (persistor?.onSet) {
136144
if (rateLimitedPersistRef.current) {
137-
// Use rate limited persist
138-
rateLimitedPersistRef.current.func(key, newState);
145+
rateLimitedPersistRef.current.func(key, newData);
139146
} else {
140-
// Immediate persist for non-rate-limited operations
141-
try {
142-
await Promise.resolve(persistor.onSet(key, newState));
143-
} catch (error) {
147+
Promise.resolve(persistor.onSet(key, newData)).catch(error => {
144148
onError?.(error as Error);
145-
if (!retryOnError) {
146-
throw error;
147-
}
148-
}
149+
});
149150
}
150151
}
152+
153+
return newData;
151154
};
152155

153-
if (typeof data !== 'function') {
154-
await setPersist(data);
155-
return data;
156-
}
156+
const mutateOpts = typeof opts === 'boolean'
157+
? { revalidate: opts }
158+
: opts ? { ...opts } : { revalidate: false };
157159

158-
const mutatorCallback = data as StateMutatorCallback<T>;
159-
const newData = await Promise.resolve(mutatorCallback(actualCurrentData as T));
160-
await setPersist(newData);
161-
return newData;
162-
}, opts);
163-
// eslint-disable-next-line react-hooks/exhaustive-deps
164-
}, [mutate, key, persistor?.onSet, onError, retryOnError, cacheKey]);
160+
mutate(mutator, mutateOpts);
161+
},
162+
// eslint-disable-next-line react-hooks/exhaustive-deps
163+
[mutate, key, persistor?.onSet, onError]
164+
);
165165

166166
const returnObject = useMemo(() => ({
167167
...swrResponse,
@@ -170,7 +170,6 @@ export function useStore<T, E = any>(
170170
isPersisting
171171
}), [swrResponse, isLoading, error, isPersisting]);
172172

173-
// Initialize rate limited persist function dengan proper cleanup
174173
useEffect(() => {
175174
if (rateLimit && !rateLimitedPersistRef.current) {
176175
const rateLimitedPersist = createRateLimitedPersist();
@@ -182,14 +181,14 @@ export function useStore<T, E = any>(
182181
}
183182
}
184183

185-
// Cleanup function
186184
return () => {
187185
if (rateLimitedPersistRef.current) {
188186
rateLimitedPersistRef.current.cleanup();
189187
rateLimitedPersistRef.current = null;
190188
}
191189
};
192-
}, [rateLimit, createRateLimitedPersist]);
190+
// eslint-disable-next-line react-hooks/exhaustive-deps
191+
}, [createRateLimitedPersist]);
193192

194193
return [
195194
state as T,

packages/swr-global-state/src/lib/utils.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { Key } from "swr";
1+
import type { Key } from 'swr';
22

33
export type RateLimitType = 'debounce' | 'throttle';
44

55
export type RateLimitConfig<T> = {
66
type: RateLimitType;
77
delay: number;
8-
// Optional custom function untuk advanced use cases
8+
// Optional custom function for advanced use cases
99
customFunction?: (func: (key: Key, data: T) => Promise<void>, delay: number) => (key: Key, data: T) => void;
1010
};
1111

@@ -19,7 +19,7 @@ export function debounceWithCleanup<T extends(...args: any[]) => any>(func: T, d
1919
debouncedFunc: T;
2020
cleanup: () => void;
2121
} {
22-
let timeoutId: NodeJS.Timeout;
22+
let timeoutId: ReturnType<typeof setTimeout>;
2323

2424
const debouncedFunc = ((...args: any[]) => {
2525
clearTimeout(timeoutId);
@@ -44,7 +44,7 @@ export function throttleWithCleanup<T extends(...args: any[]) => any>(func: T, d
4444
cleanup: () => void;
4545
} {
4646
let lastCall = 0;
47-
let timeoutId: NodeJS.Timeout;
47+
let timeoutId: ReturnType<typeof setTimeout>;
4848

4949
const throttledFunc = ((...args: any[]) => {
5050
const now = Date.now();
@@ -88,6 +88,7 @@ export function createRateLimitedFunctionWithCleanup<T>(
8888
if (config.customFunction) {
8989
return {
9090
rateLimitedFunc: config.customFunction(func, config.delay),
91+
// eslint-disable-next-line @typescript-eslint/no-empty-function
9192
cleanup: () => {} // Custom function should handle its own cleanup
9293
};
9394
}
@@ -113,6 +114,7 @@ export function createRateLimitedFunctionWithCleanup<T>(
113114
default:
114115
return {
115116
rateLimitedFunc: wrappedFunc as any,
117+
// eslint-disable-next-line @typescript-eslint/no-empty-function
116118
cleanup: () => {}
117119
};
118120
}

packages/swr-global-state/src/test/setup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import '@testing-library/jest-dom';
22
import { vi, afterEach } from 'vitest';
33
import { cleanup } from '@testing-library/react';
44

5-
// Cleanup setelah setiap test
5+
// Cleanup after every test
66
afterEach(() => {
77
cleanup();
88
vi.clearAllMocks();
99
});
1010

11-
// Mock untuk localStorage jika diperlukan
11+
// Mock for localStorage if needed
1212
Object.defineProperty(window, 'localStorage', {
1313
value: {
1414
getItem: vi.fn(),

0 commit comments

Comments
 (0)