Skip to content

Commit

Permalink
Merge pull request #10 from react18-tools/update-options-runtime
Browse files Browse the repository at this point in the history
Update-options-runtime
  • Loading branch information
mayank1513 authored Dec 30, 2023
2 parents d1caed8 + 5f48070 commit 04de9a9
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 72 deletions.
58 changes: 53 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- ✅ Share state between multiple browsing contexts
- ✅ Additional control over which fields to `persist-and-sync` and which to ignore
- ✅ Optimized for performance using memoization and closures.
- ✅ Update options at runtime by setting `__persistNSyncOptions` in your store.

## Install

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

## Advanced Usage (Customizations)

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.
### PersistNSyncOptions

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.

```typescript
type PersistNSyncOptionsType = {
Expand Down Expand Up @@ -95,13 +98,58 @@ export const useMyStore = create<MyStoreType>()(

> It is good to note here that each element of `include` and `exclude` array can either be a string or a regular expression.
> 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.
> You can specify whether to use either `"localStorage"`, `"sessionStorage"`, or `"cookies"` to persist the state - default `"localStorage"`.
> 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.
### Updating options at runtime

Since version 1.2, you can also update the options at runTime by setting `__persistNSyncOptions` in your Zustand state.

**Example**

```ts
interface StoreWithOptions {
count: number;
_count: number;
__persistNSyncOptions: PersistNSyncOptionsType;
setCount: (c: number) => void;
set_Count: (c: number) => void;
setOptions: (__persistNSyncOptions: PersistNSyncOptionsType) => void;
}

const defaultOptions = { name: "example", include: [/count/], exclude: [/^_/] };

export const useStoreWithOptions = create<StoreWithOptions>(
persistNSync(
set => ({
count: 0,
_count: 0 /** skipped as it matches the regexp provided */,
__persistNSyncOptions: defaultOptions,
setCount: count => set(state => ({ ...state, count })),
set_Count: _count => set(state => ({ ...state, _count })),
setOptions: __persistNSyncOptions => set(state => ({ ...state, __persistNSyncOptions })),
}),
defaultOptions,
),
);
```

### Clear Storage

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.

```ts
import { clearStorage } from "persist-and-sync";

...
clearStorage("my-store", "cookies");
...
```

## Legacy / Deprecated

#### Ignore / filter out fields based on regExp
#### Ignore/filter out fields based on regExp

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.
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.

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

### Exact match

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

### Ignore multiple fields with exact match

Expand Down
7 changes: 7 additions & 0 deletions examples/nextjs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# nextjs-example

## 1.0.5

### Patch Changes

- Updated dependencies [933c240]
- [email protected]

## 1.0.4

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nextjs-example",
"version": "1.0.4",
"version": "1.0.5",
"private": true,
"scripts": {
"dev": "next dev --port 3001",
Expand All @@ -17,7 +17,7 @@
},
"devDependencies": {
"@next/eslint-plugin-next": "^14.0.4",
"@types/node": "^20.10.5",
"@types/node": "^20.10.6",
"@types/react": "^18.2.46",
"@types/react-dom": "^18.2.18",
"eslint-config-custom": "workspace:*",
Expand Down
6 changes: 6 additions & 0 deletions packages/persist-and-sync/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# persist-and-sync

## 1.2.0

### Minor Changes

- 933c240: Add ability to change options at Runtime & add clearItem method

## 1.1.2

### Patch Changes
Expand Down
65 changes: 62 additions & 3 deletions packages/persist-and-sync/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { act, cleanup, renderHook } from "@testing-library/react";
import { act, cleanup, fireEvent, renderHook } from "@testing-library/react";

import { afterEach, describe, test } from "vitest";
import { useCookieStore, useMyStore } from "./store";
import { useCookieStore, useMyStore, useStoreWithOptions } from "./store";

describe.concurrent("Setting state", () => {
describe.concurrent("Persist and Sync", () => {
afterEach(cleanup);
test("test initial state", async ({ expect }) => {
const { result } = renderHook(() => useMyStore());
Expand All @@ -23,4 +23,63 @@ describe.concurrent("Setting state", () => {
expect(result.current._count).toBe(6);
expect(localStorage.getItem("example")).not.toBe('{"count":6}');
});

test("store with __persistNSyncOptions", async ({ expect }) => {
const { result } = renderHook(() => useStoreWithOptions());
act(() => result.current.set_Count(6));
expect(result.current._count).toBe(6);
expect(localStorage.getItem("example")).not.toContain('"_count":6');
act(() =>
result.current.setOptions({
...result.current.__persistNSyncOptions,
include: [],
exclude: [],
}),
);

act(() => result.current.set_Count(10));
expect(result.current._count).toBe(10);
expect(localStorage.getItem("example")).toContain('"_count":10');
});

test("Change storage type", async ({ expect }) => {
const { result } = renderHook(() => useStoreWithOptions());
act(() => result.current.setCount(6));
expect(result.current.count).toBe(6);
expect(localStorage.getItem("example")).toContain('"count":6');
act(() =>
result.current.setOptions({
...result.current.__persistNSyncOptions,
storage: "sessionStorage",
}),
);

act(() =>
result.current.setOptions({
...result.current.__persistNSyncOptions,
storage: "cookies",
}),
);

act(() => result.current.setCount(120));

act(() =>
result.current.setOptions({
...result.current.__persistNSyncOptions,
storage: "localStorage",
}),
);

act(() => result.current.setCount(20));
expect(result.current.count).toBe(20);
expect(localStorage.getItem("example")).toContain('"count":20');
});

test("Storage event", async ({ expect }) => {
const hook = renderHook(() => useMyStore());
await act(() =>
fireEvent(window, new StorageEvent("storage", { key: "example", newValue: '{"count":6}' })),
);
expect(hook.result.current.count).toBe(6);
});
});
33 changes: 29 additions & 4 deletions packages/persist-and-sync/__tests__/store.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { create } from "../vitest.setup";
import { persistNSync } from "../src";
import { PersistNSyncOptionsType, persistNSync } from "../src";

type MyStoreType = {
interface MyStoreType {
count: number;
_count: number;
setCount: (c: number) => void;
set_Count: (c: number) => void;
};
}

export const useMyStore = create<MyStoreType>(
persistNSync(
Expand All @@ -16,7 +16,7 @@ export const useMyStore = create<MyStoreType>(
setCount: count => set(state => ({ ...state, count })),
set_Count: _count => set(state => ({ ...state, _count })),
}),
{ name: "example", exclude: [/^_/] },
{ name: "example", exclude: [/^_/], initDelay: 0 },
),
);

Expand All @@ -31,3 +31,28 @@ export const useCookieStore = create<MyStoreType>(
{ name: "example", include: [/count/], exclude: [/^_/], storage: "cookies" },
),
);

interface StoreWithOptions {
count: number;
_count: number;
__persistNSyncOptions: PersistNSyncOptionsType;
setCount: (c: number) => void;
set_Count: (c: number) => void;
setOptions: (__persistNSyncOptions: PersistNSyncOptionsType) => void;
}

const defaultOptions = { name: "example", include: [/count/], exclude: [/^_/] };

export const useStoreWithOptions = create<StoreWithOptions>(
persistNSync(
set => ({
count: 0,
_count: 0 /** skipped as it matches the regexp provided */,
__persistNSyncOptions: defaultOptions,
setCount: count => set(state => ({ ...state, count })),
set_Count: _count => set(state => ({ ...state, _count })),
setOptions: __persistNSyncOptions => set(state => ({ ...state, __persistNSyncOptions })),
}),
defaultOptions,
),
);
4 changes: 2 additions & 2 deletions packages/persist-and-sync/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "persist-and-sync",
"author": "Mayank Kumar Chaudhari <https://mayank-chaudhari.vercel.app>",
"version": "1.1.2",
"version": "1.2.0",
"description": "Zustand middleware to easily persist and sync Zustand state between tabs and windows",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand All @@ -26,7 +26,7 @@
},
"devDependencies": {
"@testing-library/react": "^14.1.2",
"@types/node": "^20.10.5",
"@types/node": "^20.10.6",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^1.1.0",
"jsdom": "^23.0.1",
Expand Down
Loading

0 comments on commit 04de9a9

Please sign in to comment.