Skip to content

Commit 04de9a9

Browse files
authored
Merge pull request #10 from react18-tools/update-options-runtime
Update-options-runtime
2 parents d1caed8 + 5f48070 commit 04de9a9

File tree

9 files changed

+221
-72
lines changed

9 files changed

+221
-72
lines changed

README.md

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- ✅ Share state between multiple browsing contexts
1414
- ✅ Additional control over which fields to `persist-and-sync` and which to ignore
1515
- ✅ Optimized for performance using memoization and closures.
16+
- ✅ Update options at runtime by setting `__persistNSyncOptions` in your store.
1617

1718
## Install
1819

@@ -60,7 +61,9 @@ const useStore = create<MyStore>(
6061

6162
## Advanced Usage (Customizations)
6263

63-
In several cases you might want to exclude several fields from syncing. To support this scenario, we provide a mechanism to exclude fields based on list of fields or regular expression.
64+
### PersistNSyncOptions
65+
66+
In several cases, you might want to exclude several fields from syncing. To support this scenario, we provide a mechanism to exclude fields based on a list of fields or regular expressions.
6467

6568
```typescript
6669
type PersistNSyncOptionsType = {
@@ -95,13 +98,58 @@ export const useMyStore = create<MyStoreType>()(
9598

9699
> It is good to note here that each element of `include` and `exclude` array can either be a string or a regular expression.
97100
> To use regular expression, you should either use `new RegExp()` or `/your-expression/` syntax. Double or single quoted strings are not treated as regular expression.
98-
> You can specify whether to use either `"localStorage"`, `"sessionStorage"`, or `"cookies"` to persist the state - default `"localStorage"`.
101+
> You can specify whether to use either `"localStorage"`, `"sessionStorage"`, or `"cookies"` to persist the state - default `"localStorage"`. Please note that `"sessionStorage"` is not persisted. Hence can be used for sync only scenarios.
102+
103+
### Updating options at runtime
104+
105+
Since version 1.2, you can also update the options at runTime by setting `__persistNSyncOptions` in your Zustand state.
106+
107+
**Example**
108+
109+
```ts
110+
interface StoreWithOptions {
111+
count: number;
112+
_count: number;
113+
__persistNSyncOptions: PersistNSyncOptionsType;
114+
setCount: (c: number) => void;
115+
set_Count: (c: number) => void;
116+
setOptions: (__persistNSyncOptions: PersistNSyncOptionsType) => void;
117+
}
118+
119+
const defaultOptions = { name: "example", include: [/count/], exclude: [/^_/] };
120+
121+
export const useStoreWithOptions = create<StoreWithOptions>(
122+
persistNSync(
123+
set => ({
124+
count: 0,
125+
_count: 0 /** skipped as it matches the regexp provided */,
126+
__persistNSyncOptions: defaultOptions,
127+
setCount: count => set(state => ({ ...state, count })),
128+
set_Count: _count => set(state => ({ ...state, _count })),
129+
setOptions: __persistNSyncOptions => set(state => ({ ...state, __persistNSyncOptions })),
130+
}),
131+
defaultOptions,
132+
),
133+
);
134+
```
135+
136+
### Clear Storage
137+
138+
Starting from version 1.2, you can also clear the persisted data by calling `clearStorage` function. It takes `name` of your store (`name` passed in `options` while creating the store), and optional `storageType` parameters.
139+
140+
```ts
141+
import { clearStorage } from "persist-and-sync";
142+
143+
...
144+
clearStorage("my-store", "cookies");
145+
...
146+
```
99147

100148
## Legacy / Deprecated
101149

102-
#### Ignore / filter out fields based on regExp
150+
#### Ignore/filter out fields based on regExp
103151

104-
In several cases you might want to exclude several fields from syncing. To support this scenario, we provide a mechanism to exclude fields based on regExp. Just pass `regExpToIgnore` (optional - default -> undefined) in options object.
152+
In several cases, you might want to exclude several fields from syncing. To support this scenario, we provide a mechanism to exclude fields based on regExp. Just pass `regExpToIgnore` (optional - default -> undefined) in the options object.
105153

106154
```ts
107155
// to ignore fields containing a slug
@@ -122,7 +170,7 @@ For more details about regExp check out - [JS RegExp](https://www.w3schools.com/
122170

123171
### Exact match
124172

125-
For exactly matching a parameter/field use `/^your-field-name$/`. `^` forces match from the first caracter and similarly, `$` forces match until the last character.
173+
For exactly matching a parameter/field use `/^your-field-name$/`. `^` forces match from the first character and similarly, `$` forces match until the last character.
126174

127175
### Ignore multiple fields with exact match
128176

examples/nextjs/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# nextjs-example
22

3+
## 1.0.5
4+
5+
### Patch Changes
6+
7+
- Updated dependencies [933c240]
8+
9+
310
## 1.0.4
411

512
### Patch Changes

examples/nextjs/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nextjs-example",
3-
"version": "1.0.4",
3+
"version": "1.0.5",
44
"private": true,
55
"scripts": {
66
"dev": "next dev --port 3001",
@@ -17,7 +17,7 @@
1717
},
1818
"devDependencies": {
1919
"@next/eslint-plugin-next": "^14.0.4",
20-
"@types/node": "^20.10.5",
20+
"@types/node": "^20.10.6",
2121
"@types/react": "^18.2.46",
2222
"@types/react-dom": "^18.2.18",
2323
"eslint-config-custom": "workspace:*",

packages/persist-and-sync/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# persist-and-sync
22

3+
## 1.2.0
4+
5+
### Minor Changes
6+
7+
- 933c240: Add ability to change options at Runtime & add clearItem method
8+
39
## 1.1.2
410

511
### Patch Changes

packages/persist-and-sync/__tests__/index.test.ts

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { act, cleanup, renderHook } from "@testing-library/react";
1+
import { act, cleanup, fireEvent, renderHook } from "@testing-library/react";
22

33
import { afterEach, describe, test } from "vitest";
4-
import { useCookieStore, useMyStore } from "./store";
4+
import { useCookieStore, useMyStore, useStoreWithOptions } from "./store";
55

6-
describe.concurrent("Setting state", () => {
6+
describe.concurrent("Persist and Sync", () => {
77
afterEach(cleanup);
88
test("test initial state", async ({ expect }) => {
99
const { result } = renderHook(() => useMyStore());
@@ -23,4 +23,63 @@ describe.concurrent("Setting state", () => {
2323
expect(result.current._count).toBe(6);
2424
expect(localStorage.getItem("example")).not.toBe('{"count":6}');
2525
});
26+
27+
test("store with __persistNSyncOptions", async ({ expect }) => {
28+
const { result } = renderHook(() => useStoreWithOptions());
29+
act(() => result.current.set_Count(6));
30+
expect(result.current._count).toBe(6);
31+
expect(localStorage.getItem("example")).not.toContain('"_count":6');
32+
act(() =>
33+
result.current.setOptions({
34+
...result.current.__persistNSyncOptions,
35+
include: [],
36+
exclude: [],
37+
}),
38+
);
39+
40+
act(() => result.current.set_Count(10));
41+
expect(result.current._count).toBe(10);
42+
expect(localStorage.getItem("example")).toContain('"_count":10');
43+
});
44+
45+
test("Change storage type", async ({ expect }) => {
46+
const { result } = renderHook(() => useStoreWithOptions());
47+
act(() => result.current.setCount(6));
48+
expect(result.current.count).toBe(6);
49+
expect(localStorage.getItem("example")).toContain('"count":6');
50+
act(() =>
51+
result.current.setOptions({
52+
...result.current.__persistNSyncOptions,
53+
storage: "sessionStorage",
54+
}),
55+
);
56+
57+
act(() =>
58+
result.current.setOptions({
59+
...result.current.__persistNSyncOptions,
60+
storage: "cookies",
61+
}),
62+
);
63+
64+
act(() => result.current.setCount(120));
65+
66+
act(() =>
67+
result.current.setOptions({
68+
...result.current.__persistNSyncOptions,
69+
storage: "localStorage",
70+
}),
71+
);
72+
73+
act(() => result.current.setCount(20));
74+
expect(result.current.count).toBe(20);
75+
expect(localStorage.getItem("example")).toContain('"count":20');
76+
});
77+
78+
test("Storage event", async ({ expect }) => {
79+
const hook = renderHook(() => useMyStore());
80+
await act(() =>
81+
fireEvent(window, new StorageEvent("storage", { key: "example", newValue: '{"count":6}' })),
82+
);
83+
expect(hook.result.current.count).toBe(6);
84+
});
2685
});

packages/persist-and-sync/__tests__/store.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { create } from "../vitest.setup";
2-
import { persistNSync } from "../src";
2+
import { PersistNSyncOptionsType, persistNSync } from "../src";
33

4-
type MyStoreType = {
4+
interface MyStoreType {
55
count: number;
66
_count: number;
77
setCount: (c: number) => void;
88
set_Count: (c: number) => void;
9-
};
9+
}
1010

1111
export const useMyStore = create<MyStoreType>(
1212
persistNSync(
@@ -16,7 +16,7 @@ export const useMyStore = create<MyStoreType>(
1616
setCount: count => set(state => ({ ...state, count })),
1717
set_Count: _count => set(state => ({ ...state, _count })),
1818
}),
19-
{ name: "example", exclude: [/^_/] },
19+
{ name: "example", exclude: [/^_/], initDelay: 0 },
2020
),
2121
);
2222

@@ -31,3 +31,28 @@ export const useCookieStore = create<MyStoreType>(
3131
{ name: "example", include: [/count/], exclude: [/^_/], storage: "cookies" },
3232
),
3333
);
34+
35+
interface StoreWithOptions {
36+
count: number;
37+
_count: number;
38+
__persistNSyncOptions: PersistNSyncOptionsType;
39+
setCount: (c: number) => void;
40+
set_Count: (c: number) => void;
41+
setOptions: (__persistNSyncOptions: PersistNSyncOptionsType) => void;
42+
}
43+
44+
const defaultOptions = { name: "example", include: [/count/], exclude: [/^_/] };
45+
46+
export const useStoreWithOptions = create<StoreWithOptions>(
47+
persistNSync(
48+
set => ({
49+
count: 0,
50+
_count: 0 /** skipped as it matches the regexp provided */,
51+
__persistNSyncOptions: defaultOptions,
52+
setCount: count => set(state => ({ ...state, count })),
53+
set_Count: _count => set(state => ({ ...state, _count })),
54+
setOptions: __persistNSyncOptions => set(state => ({ ...state, __persistNSyncOptions })),
55+
}),
56+
defaultOptions,
57+
),
58+
);

packages/persist-and-sync/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "persist-and-sync",
33
"author": "Mayank Kumar Chaudhari <https://mayank-chaudhari.vercel.app>",
4-
"version": "1.1.2",
4+
"version": "1.2.0",
55
"description": "Zustand middleware to easily persist and sync Zustand state between tabs and windows",
66
"main": "dist/index.js",
77
"types": "dist/index.d.ts",
@@ -26,7 +26,7 @@
2626
},
2727
"devDependencies": {
2828
"@testing-library/react": "^14.1.2",
29-
"@types/node": "^20.10.5",
29+
"@types/node": "^20.10.6",
3030
"@vitejs/plugin-react": "^4.2.1",
3131
"@vitest/coverage-v8": "^1.1.0",
3232
"jsdom": "^23.0.1",

0 commit comments

Comments
 (0)