Heuristic risk: Medium (2 pts)
This is a rule-based heuristic, not a probability and not a defect prediction. The risk label and the related-file list are separate signals.
3 of the 3 related file(s) absent from this PR have Tier A co-change support.
Not applicable: at least one Tier A related file is present, which scores instead.
1 non-test source file(s) changed, below the threshold of 4.
No test gap: every changed source file either has no likely test or has a likely test inside this PR.
No changed file has 3 or more direct importers in this PR's import context.
not evaluated: import cycles are only known for the pinned repository snapshot and this PR was analyzed at its own base commit.
Changed files (3)
Source files
View diff excerpt (21 lines)
diff --git a/src/middleware/persist.ts b/src/middleware/persist.ts
index 3f2008c..d81e629 100644
--- a/src/middleware/persist.ts
+++ b/src/middleware/persist.ts
@@ -85,6 +85,7 @@ interface StorePersist<S extends State, Ps> {
hasHydrated: () => boolean
onHydrate: (fn: PersistListener<S>) => () => void
onFinishHydration: (fn: PersistListener<S>) => () => void
+ getOptions: () => Partial<PersistOptions<S, Ps>>
}
}
@@ -276,6 +277,7 @@ const persistImpl: PersistImpl = (config, baseOptions) => (set, get, api) => {
clearStorage: () => {
storage?.removeItem(options.name)
},
+ getOptions: () => options,
rehydrate: () => hydrate() as Promise<void>,
hasHydrated: () => hasHydrated,
onHydrate: (cb) => {
Test files
View diff excerpt (29 lines)
diff --git a/tests/persistSync.test.tsx b/tests/persistSync.test.tsx
index 3cece94..aebe09e 100644
--- a/tests/persistSync.test.tsx
+++ b/tests/persistSync.test.tsx
@@ -381,6 +381,23 @@ describe('persist middleware with sync configuration', () => {
)
})
+ it('can access the options through the api', () => {
+ const storage = {
+ getItem: () => null,
+ setItem: jest.fn(),
+ removeItem: () => {},
+ }
+
+ const useStore = create(
+ persist(() => ({ count: 0 }), {
+ name: 'test-storage',
+ getStorage: () => storage,
+ })
+ )
+ expect(useStore.persist.getOptions().name).toBeDefined()
+ expect(useStore.persist.getOptions().name).toBe('test-storage')
+ })
+
it('can change the options through the api', () => {
const setItemSpy = jest.fn()
Other files
View diff excerpt (showing 80 of 550 lines)
Truncated excerpt: showing 80 of 550 lines
diff --git a/docs/persisting-store-data.md b/docs/persisting-store-data.md
new file mode 100644
index 0000000..1ea4b1f
--- /dev/null
+++ b/docs/persisting-store-data.md
@@ -0,0 +1,543 @@
+# Persist middleware
+
+The persist middleware enables you to store your Zustand state in a storage (e.g. `localStorage`, `AsyncStorage`, `IndexedDB`, etc...) thus persisting it's data.
+
+Note that this middleware does support both synchronous storages (e.g. `localStorage`) and asynchronous storages (e.g. `AsyncStorage`), but using an asynchronous storage does come with a cost.
+See [Hydration and asynchronous storages](#Hydration-and-asynchronous-storages) for more details.
+
+Quick example:
+
+```ts
+import create from 'zustand'
+import { persist } from 'zustand/middleware'
+
+export const useStore = create(
+ persist(
+ (set, get) => ({
+ fishes: 0,
+ addAFish: () => set({ fishes: get().fishes + 1 }),
+ }),
+ {
+ name: 'food-storage', // name of item in the storage (must be unique)
+ getStorage: () => sessionStorage, // (optional) by default the 'localStorage' is used
+ }
+ )
+)
+```
+
+See [Options](#Options) for more details.
+
+## Options
+
+### `name`
+
+This is the only required option.
+The given name is going to be the key used to store your Zustand state in the storage, so it must be unique.
+
+### `getStorage`
+
+> Default: `() => localStorage`
+
+Enables you to use your own storage.
+Simply pass a function that returns the storage you want to use.
+
+Example:
+
+```ts
+export const useStore = create(
+ persist(
+ (set, get) => ({
+ // ...
+ }),
+ {
+ // ...
+ getStorage: () => AsyncStorage,
+ }
+ )
+)
+```
+
+The given storage must match the following interface:
+
+```ts
+interface Storage {
+ getItem: (name: string) => string | null | Promise<string | null>
+ setItem: (name: string, value: string) => void | Promise<void>
+ removeItem: (name: string) => void | Promise<void>
+}
+```
+
+### `serialize`
+
+> Schema: `(state: Object) => string | Promise<string>`
+
+> Default: `(state) => JSON.stringify(state)`Related files not in this PR (3)
Files that historically change together with this PR's changed files in prior commits.
In history before this PR, 11 of the 16 commits that changed tests/persistSync.test.tsx also changed tests/persistAsync.test.tsx. The other direction: 11 of the 19 commits that changed tests/persistAsync.test.tsx also changed tests/persistSync.test.tsx. No direct import link was found between them at the PR base snapshot. One changed file in this PR points at it.
Lift 18.16: this file changes together with the changed file far more often than its overall change rate would suggest.
Snapshot code link: This PR was analyzed at its own base commit, not the pinned repository snapshot, so the snapshot code viewer is not available for it.
In history before this PR, 6 of the 9 commits that changed src/middleware/persist.ts also changed src/middleware/devtools.ts. The other direction: 6 of the 19 commits that changed src/middleware/devtools.ts also changed src/middleware/persist.ts. No direct import link was found between them at the PR base snapshot. One changed file in this PR points at it.
Lift 17.61: this file changes together with the changed file far more often than its overall change rate would suggest.
Snapshot code link: This PR was analyzed at its own base commit, not the pinned repository snapshot, so the snapshot code viewer is not available for it.
In history before this PR, 5 of the 9 commits that changed src/middleware/persist.ts also changed src/middleware/subscribeWithSelector.ts. The other direction: 5 of the 7 commits that changed src/middleware/subscribeWithSelector.ts also changed src/middleware/persist.ts. No direct import link was found between them at the PR base snapshot. One changed file in this PR points at it.
Lift 39.84: this file changes together with the changed file far more often than its overall change rate would suggest.
Snapshot code link: This PR was analyzed at its own base commit, not the pinned repository snapshot, so the snapshot code viewer is not available for it.
Import context (up to two levels)
- src/middleware.ts
- src/vanilla.ts
- src/index.ts(via src/vanilla.ts)
- src/middleware/combine.ts(via src/vanilla.ts)
- src/middleware/devtools.ts(via src/vanilla.ts)
- src/middleware/immer.ts(via src/vanilla.ts)
- src/middleware/redux.ts(via src/vanilla.ts)
- src/middleware/subscribeWithSelector.ts(via src/vanilla.ts)
- src/react.ts(via src/vanilla.ts)
Suggested tests (filename heuristic)
The filename heuristic could not identify any matching test files.
Prepared review
Prepared • Stored text from offline analysis, not a live AI responseDiff-grounded observations
“getOptions: () => options”
The persist api gains getOptions(), which returns the current options object held by the middleware.
“getOptions: () => Partial<PersistOptions<S, Ps>>”
The StorePersist interface declares getOptions: () => Partial<PersistOptions<S, Ps>>.
“expect(useStore.persist.getOptions().name).toBe('test-storage')”
A new sync-storage test checks that getOptions() reports the configured storage name.
“The persist middleware enables you to store your Zustand state in a storage”
A new documentation page for the persist middleware was added in the same PR.