Skip to content

Commit fa334f9

Browse files
committed
support persist middleware
1 parent f67b324 commit fa334f9

File tree

8 files changed

+128
-15
lines changed

8 files changed

+128
-15
lines changed

README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,23 @@ npm install zustand zustand-valtio valtio
1616
## Usage
1717

1818
```jsx
19-
import { createWithProxy } from 'zustand-valtio';
20-
21-
const useCounterState = createWithProxy({
22-
count: 0,
23-
inc() {
24-
this.count++;
25-
},
26-
});
19+
import { create } from 'zustand';
20+
import { withProxy } from 'zustand-valtio';
21+
22+
const useCounterState = create(
23+
withProxy({
24+
count: 0,
25+
inc() {
26+
this.count++;
27+
},
28+
}),
29+
);
2730

2831
const Counter = () => {
2932
const count = useCounterState((state) => state.count);
3033
const inc = useCounterState((state) => state.inc);
3134
// Or this works too
32-
// const inc = () => ++useCounterState.proxyState.count;
35+
// const inc = () => ++useCounterState.getProxyState().count;
3336
return (
3437
<>
3538
<div>Count: {count}</div>
@@ -60,6 +63,7 @@ and open <http://localhost:8080> in your web browser.
6063
You can also try them directly:
6164
[01](https://stackblitz.com/github/zustandjs/zustand-valtio/tree/main/examples/01_counter)
6265
[02](https://stackblitz.com/github/zustandjs/zustand-valtio/tree/main/examples/02_methods)
66+
[03](https://stackblitz.com/github/zustandjs/zustand-valtio/tree/main/examples/03_middleware)
6367

6468
## Tweets
6569

examples/03_middleware/index.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<html>
2+
<head>
3+
<title>example</title>
4+
</head>
5+
<body>
6+
<div id="root"></div>
7+
<script type="module" src="/src/main.tsx"></script>
8+
</body>
9+
</html>

examples/03_middleware/package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "example",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"dependencies": {
7+
"react": "latest",
8+
"react-dom": "latest",
9+
"valtio": "latest",
10+
"zustand": "latest",
11+
"zustand-valtio": "latest"
12+
},
13+
"devDependencies": {
14+
"@types/react": "latest",
15+
"@types/react-dom": "latest",
16+
"typescript": "latest",
17+
"vite": "latest"
18+
},
19+
"scripts": {
20+
"dev": "vite"
21+
}
22+
}

examples/03_middleware/src/app.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { create } from 'zustand';
2+
import { persist } from 'zustand/middleware';
3+
import { withProxy } from 'zustand-valtio';
4+
5+
const useCounterState = create(
6+
persist(
7+
withProxy({
8+
count: 0,
9+
inc() {
10+
this.count++;
11+
},
12+
}),
13+
{ name: 'counter' },
14+
),
15+
);
16+
17+
const Counter = () => {
18+
const count = useCounterState((state) => state.count);
19+
const inc = useCounterState((state) => state.inc);
20+
return (
21+
<>
22+
<div>Count: {count}</div>
23+
<button type="button" onClick={inc}>
24+
+1
25+
</button>
26+
</>
27+
);
28+
};
29+
30+
const App = () => (
31+
<div>
32+
<Counter />
33+
</div>
34+
);
35+
36+
export default App;

examples/03_middleware/src/main.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { StrictMode } from 'react';
2+
import { createRoot } from 'react-dom/client';
3+
4+
import App from './app';
5+
6+
createRoot(document.getElementById('root')!).render(
7+
<StrictMode>
8+
<App />
9+
</StrictMode>,
10+
);

examples/03_middleware/tsconfig.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"compilerOptions": {
3+
"strict": true,
4+
"target": "es2018",
5+
"esModuleInterop": true,
6+
"module": "esnext",
7+
"moduleResolution": "bundler",
8+
"skipLibCheck": true,
9+
"allowJs": true,
10+
"noUncheckedIndexedAccess": true,
11+
"exactOptionalPropertyTypes": true,
12+
"jsx": "react-jsx"
13+
}
14+
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"test:types:examples": "tsc -p examples --noEmit",
4242
"test:spec": "vitest run",
4343
"examples:01_counter": "DIR=01_counter vite",
44-
"examples:02_methods": "DIR=02_methods vite"
44+
"examples:02_methods": "DIR=02_methods vite",
45+
"examples:03_middleware": "DIR=03_middleware vite"
4546
},
4647
"keywords": [
4748
"react",

src/index.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,34 +37,51 @@ type WithProxyImpl = <T extends object>(
3737
initialObject: T,
3838
) => StateCreator<Snapshot<T>, [], []>;
3939

40-
const withProxyImpl: WithProxyImpl = (initialObject) => (set, _get, api) => {
40+
const withProxyImpl: WithProxyImpl = (initialObject) => (set, get, api) => {
4141
type AnyObject = Record<string, unknown>;
4242
const proxyState = proxy(initialObject);
4343
let mutating = 0;
44+
let updating = 0;
4445
const updateState = () => {
4546
if (!mutating) {
47+
++updating;
4648
set(snapshot(proxyState), true);
49+
--updating;
4750
}
4851
};
4952
Object.keys(proxyState).forEach((key) => {
5053
const fn = (proxyState as AnyObject)[key];
54+
// TODO this doesn't handle nested objects
5155
if (typeof fn === 'function') {
5256
(proxyState as AnyObject)[key] = (...args: never[]) => {
5357
try {
54-
mutating += 1;
58+
++mutating;
5559
return fn.apply(proxyState, args);
5660
} finally {
57-
mutating -= 1;
61+
--mutating;
5862
updateState();
5963
}
6064
};
6165
}
6266
});
63-
type Api = StoreApi<Snapshot<typeof initialObject>> &
64-
StoreWithProxy<typeof initialObject>;
67+
type Api = StoreApi<unknown> & StoreWithProxy<typeof initialObject>;
6568
delete (api as Api).setState;
6669
(api as Api).getProxyState = () => proxyState;
6770
subscribe(proxyState, updateState, true);
71+
api.subscribe(() => {
72+
if (!updating) {
73+
// HACK for persist hydration
74+
const state = get() as AnyObject;
75+
Object.keys(state).forEach((key) => {
76+
const val = state[key];
77+
// TODO this doesn't handle nested objects
78+
if (typeof val !== 'function') {
79+
// XXX this will throw if val is a snapshot
80+
(proxyState as AnyObject)[key] = val;
81+
}
82+
});
83+
}
84+
});
6885
return snapshot(proxyState);
6986
};
7087

0 commit comments

Comments
 (0)