mirror of
https://github.com/pmndrs/zustand.git
synced 2025-12-08 19:45:52 +00:00
Compare commits
8 Commits
2a419514d1
...
81df20a050
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81df20a050 | ||
|
|
18ab9e2615 | ||
|
|
7ab97535d7 | ||
|
|
77f5de91d6 | ||
|
|
d0c71ce15c | ||
|
|
d25a4d226f | ||
|
|
ab5a98b187 | ||
|
|
6422fa8898 |
@ -487,7 +487,7 @@ const useBearStore = create<BearState>()(
|
||||
)
|
||||
```
|
||||
|
||||
A more complete TypeScript guide is [here](docs/guides/typescript.md).
|
||||
A more detailed TypeScript guide is [here](docs/guides/beginner-typescript.md) and [there](docs/guides/advanced-typescript.md).
|
||||
|
||||
## Best practices
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: TypeScript Guide
|
||||
title: Advanced TypeScript Guide
|
||||
nav: 7
|
||||
---
|
||||
|
||||
378
docs/guides/beginner-typescript.md
Normal file
378
docs/guides/beginner-typescript.md
Normal file
@ -0,0 +1,378 @@
|
||||
---
|
||||
title: Beginner TypeScript Guide
|
||||
nav: 7
|
||||
---
|
||||
|
||||
Zustand is a lightweight state manager, particularly used with React. Zustand avoids reducers, context, and boilerplate.
|
||||
Paired with TypeScript, you get a strongly typed store-state, actions, and selectors-with autocomplete and compile-time safety.
|
||||
|
||||
In this basic guide we’ll cover:
|
||||
|
||||
- Creating a typed store (state + actions)
|
||||
- Using the store in React components with type safety
|
||||
- Resetting the store safely with types
|
||||
- Extracting and reusing Store type (for props, tests, and utilities)
|
||||
- Composing multiple selectors and building derived state (with type inference and without extra re-renders)
|
||||
- Middlewares with TypeScript support (`combine`, `devtools`, `persist`)
|
||||
- Async actions with typed API responses
|
||||
- Working with `createWithEqualityFn` (enhanced `create` store function)
|
||||
- Structuring and coordinating multiple stores
|
||||
|
||||
### Creating a Store with State & Actions
|
||||
|
||||
Here we describe state and actions using an Typescript interface. The `<BearState>` generic forces the store to match this shape.
|
||||
This means if you forget a field or use the wrong type, TypeScript will complain. Unlike plain JS, this guarantees type-safe state management.
|
||||
The `create` function uses the curried form, which results in a store of type `UseBoundStore<StoreApi<BearState>>`.
|
||||
|
||||
```ts
|
||||
// store.ts
|
||||
import { create } from 'zustand'
|
||||
|
||||
// Define types for state & actions
|
||||
interface BearState {
|
||||
bears: number
|
||||
food: string
|
||||
feed: (food: string) => void
|
||||
}
|
||||
|
||||
// Create store using the curried form of `create`
|
||||
export const useBearStore = create<BearState>()((set) => ({
|
||||
bears: 2,
|
||||
food: 'honey',
|
||||
feed: (food) => set(() => ({ food })),
|
||||
}))
|
||||
```
|
||||
|
||||
### Using the Store in Components
|
||||
|
||||
Inside components, you can read state and call actions. Selectors `(s) => s.bears` subscribe to only what you need.
|
||||
This reduces re-renders and improves performance. JS can do this too, but with TS your IDE autocompletes state fields.
|
||||
|
||||
```tsx
|
||||
import { useBearStore } from './store'
|
||||
|
||||
function BearCounter() {
|
||||
// Select only 'bears' to avoid unnecessary re-renders
|
||||
const bears = useBearStore((s) => s.bears)
|
||||
return <h1>{bears} bears around</h1>
|
||||
}
|
||||
```
|
||||
|
||||
### Resetting the Store
|
||||
|
||||
Resetting is useful after logout or “clear session”. We use `typeof initialState` to avoid repeating property types.
|
||||
TypeScript updates automatically if `initialState` changes. This is safer and cleaner compared to JS.
|
||||
|
||||
```tsx
|
||||
import { create } from 'zustand'
|
||||
|
||||
const initialState = { bears: 0, food: 'honey' }
|
||||
|
||||
// Reuse state type dynamically
|
||||
type BearState = typeof initialState & {
|
||||
increase: (by: number) => void
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
const useBearStore = create<BearState>()((set) => ({
|
||||
...initialState,
|
||||
increase: (by) => set((s) => ({ bears: s.bears + by })),
|
||||
reset: () => set(initialState),
|
||||
}))
|
||||
|
||||
function ResetZoo() {
|
||||
const { bears, increase, reset } = useBearStore()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{bears}</div>
|
||||
<button onClick={() => increase(5)}>Increase by 5</button>
|
||||
<button onClick={reset}>Reset</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Extracting Types
|
||||
|
||||
Zustand provides a built-in helper called `ExtractState`. This is useful for tests, utility functions, or component props.
|
||||
It returns the full type of your store’s state and actions without having to manually redefine them. Extracting the Store type:
|
||||
|
||||
```ts
|
||||
// store.ts
|
||||
import { create, type ExtractState } from 'zustand'
|
||||
|
||||
export const useBearStore = create((set) => ({
|
||||
bears: 3,
|
||||
food: 'honey',
|
||||
increase: (by: number) => set((s) => ({ bears: s.bears + by })),
|
||||
}))
|
||||
|
||||
// Extract the type of the whole store state
|
||||
export type BearState = ExtractState<typeof useBearStore>
|
||||
```
|
||||
|
||||
Using extracted type in tests:
|
||||
|
||||
```ts
|
||||
// test.cy.ts
|
||||
import { BearState } from './store.ts'
|
||||
|
||||
test('should reset store', () => {
|
||||
const snapshot: BearState = useBearStore.getState()
|
||||
expect(snapshot.bears).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
```
|
||||
|
||||
and in utility function:
|
||||
|
||||
```ts
|
||||
// util.ts
|
||||
import { BearState } from './store.ts'
|
||||
|
||||
function logBearState(state: BearState) {
|
||||
console.log(`We have ${state.bears} bears eating ${state.food}`)
|
||||
}
|
||||
|
||||
logBearState(useBearStore.getState())
|
||||
```
|
||||
|
||||
### Selectors
|
||||
|
||||
#### Multiple Selectors
|
||||
|
||||
Sometimes you need more than one property. Returning an object from the selector lets you access multiple fields at once.
|
||||
However, directly destructuring properties from that object can cause unnecessary re-renders.
|
||||
To avoid this, it’s recommended to wrap the selector with `useShallow`, which prevents re-renders when the selected values remain shallowly equal.
|
||||
This is more efficient than subscribing to the whole store. TypeScript ensures you can’t accidentally misspell `bears` or `food`.
|
||||
See the [API documentation](https://zustand.docs.pmnd.rs/hooks/use-shallow) for more details on `useShallow`.
|
||||
|
||||
```tsx
|
||||
import { create } from 'zustand'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
|
||||
// Bear store with explicit types
|
||||
interface BearState {
|
||||
bears: number
|
||||
food: number
|
||||
}
|
||||
|
||||
const useBearStore = create<BearState>()(() => ({
|
||||
bears: 2,
|
||||
food: 10,
|
||||
}))
|
||||
|
||||
// In components, you can use both stores safely
|
||||
function MultipleSelectors() {
|
||||
const { bears, food } = useBearStore(
|
||||
useShallow((state) => ({ bears: state.bears, food: state.food })),
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
We have {food} units of food for {bears} bears
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Derived State with Selectors
|
||||
|
||||
Not all values need to be stored directly - some can be computed from existing state. You can derive values using selectors.
|
||||
This avoids duplication and keeps the store minimal. TypeScript ensures `bears` is a number, so math is safe.
|
||||
|
||||
```tsx
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface BearState {
|
||||
bears: number
|
||||
foodPerBear: number
|
||||
}
|
||||
|
||||
const useBearStore = create<BearState>()(() => ({
|
||||
bears: 3,
|
||||
foodPerBear: 2,
|
||||
}))
|
||||
|
||||
function TotalFood() {
|
||||
// Derived value: required amount food for all bears
|
||||
const totalFood = useBearStore((s) => s.bears * s.foodPerBear) // don't need to have extra property `{ totalFood: 6 }` in your Store
|
||||
|
||||
return <div>We need ${totalFood} jars of honey</div>
|
||||
}
|
||||
```
|
||||
|
||||
### Middlewares
|
||||
|
||||
#### `combine` middleware
|
||||
|
||||
This middleware separates initial state and actions, making the code cleaner.
|
||||
TS automatically infers types from the state and actions, no interface needed.
|
||||
This is different from JS, where type safety is missing. It’s a very popular style in TypeScript projects.
|
||||
See the [API documentation](https://zustand.docs.pmnd.rs/middlewares/combine) for more details.
|
||||
|
||||
```ts
|
||||
import { create } from 'zustand'
|
||||
import { combine } from 'zustand/middleware'
|
||||
|
||||
interface BearState {
|
||||
bears: number
|
||||
increase: () => void
|
||||
}
|
||||
|
||||
// State + actions are separated
|
||||
export const useBearStore = create<BearState>()(
|
||||
combine({ bears: 0 }, (set) => ({
|
||||
increase: () => set((s) => ({ bears: s.bears + 1 })),
|
||||
})),
|
||||
)
|
||||
```
|
||||
|
||||
#### `devtools` middleware
|
||||
|
||||
This middleware connects Zustand to Redux DevTools. You can inspect changes, time-travel, and debug state.
|
||||
It’s extremely useful in development. TS ensures your actions and state remain type-checked even here.
|
||||
See the [API documentation](https://zustand.docs.pmnd.rs/middlewares/devtools) for more details.
|
||||
|
||||
```ts
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
interface BearState {
|
||||
bears: number
|
||||
increase: () => void
|
||||
}
|
||||
|
||||
export const useBearStore = create<BearState>()(
|
||||
devtools((set) => ({
|
||||
bears: 0,
|
||||
increase: () => set((s) => ({ bears: s.bears + 1 })),
|
||||
})),
|
||||
)
|
||||
```
|
||||
|
||||
#### `persist` middleware
|
||||
|
||||
This middleware keeps your store in `localStorage` (or another storage). This means your bears survive a page refresh.
|
||||
Great for apps where persistence matters. In TS, the state type stays consistent, so no runtime surprises.
|
||||
See the [API documentation](https://zustand.docs.pmnd.rs/middlewares/persist) for more details.
|
||||
|
||||
```ts
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
interface BearState {
|
||||
bears: number
|
||||
increase: () => void
|
||||
}
|
||||
|
||||
export const useBearStore = create<BearState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
bears: 0,
|
||||
increase: () => set((s) => ({ bears: s.bears + 1 })), // <-- тип явно
|
||||
}),
|
||||
{ name: 'bear-storage' }, // localStorage key
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### Async Actions
|
||||
|
||||
Actions can be async to fetch remote data. Here we fetch bears count and update state.
|
||||
TS enforces correct API response type (`BearData`). In JS you might misspell `count` - TS prevents that.
|
||||
|
||||
```ts
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface BearData {
|
||||
count: number
|
||||
}
|
||||
|
||||
interface BearState {
|
||||
bears: number
|
||||
fetchBears: () => Promise<void>
|
||||
}
|
||||
|
||||
export const useBearStore = create<BearState>()((set) => ({
|
||||
bears: 0,
|
||||
fetchBears: async () => {
|
||||
const res = await fetch('/api/bears')
|
||||
const data: BearData = await res.json()
|
||||
|
||||
set({ bears: data.count })
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
### `createWithEqualityFn`
|
||||
|
||||
Variant of `create` with equality built-in. Useful if you always want custom equality checks.
|
||||
Not common, but shows Zustand’s flexibility. TS still keeps full type inference.
|
||||
See the [API documentation](https://zustand.docs.pmnd.rs/apis/create-with-equality-fn) for more details.
|
||||
|
||||
```ts
|
||||
import { createWithEqualityFn } from 'zustand/traditional'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
|
||||
const useBearStore = createWithEqualityFn(() => ({
|
||||
bears: 0,
|
||||
}))
|
||||
|
||||
const bears = useBearStore((s) => s.bears, Object.is)
|
||||
// or
|
||||
const bears = useBearStore((s) => ({ bears: s.bears }), shallow)
|
||||
```
|
||||
|
||||
### Multiple Stores
|
||||
|
||||
You can create more than one store for different domains. For example, `BearStore` manages bears and `FishStore` manages fish.
|
||||
This keeps state isolated and easier to maintain in larger apps. With TypeScript, each store has its own strict type - you can’t accidentally mix bears and fish.
|
||||
|
||||
```tsx
|
||||
import { create } from 'zustand'
|
||||
|
||||
// Bear store with explicit types
|
||||
interface BearState {
|
||||
bears: number
|
||||
addBear: () => void
|
||||
}
|
||||
|
||||
const useBearStore = create<BearState>()((set) => ({
|
||||
bears: 2,
|
||||
addBear: () => set((s) => ({ bears: s.bears + 1 })),
|
||||
}))
|
||||
|
||||
// Fish store with explicit types
|
||||
interface FishState {
|
||||
fish: number
|
||||
addFish: () => void
|
||||
}
|
||||
|
||||
const useFishStore = create<FishState>()((set) => ({
|
||||
fish: 5,
|
||||
addFish: () => set((s) => ({ fish: s.fish + 1 })),
|
||||
}))
|
||||
|
||||
// In components, you can use both stores safely
|
||||
function Zoo() {
|
||||
const { bears, addBear } = useBearStore()
|
||||
const { fish, addFish } = useFishStore()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{bears} bears and {fish} fish
|
||||
</div>
|
||||
<button onClick={addBear}>Add bear</button>
|
||||
<button onClick={addFish}>Add fish</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Conclusion
|
||||
|
||||
Zustand together with TypeScript provides a balance: you keep the simplicity of small, minimalistic stores, while gaining the safety of strong typing.
|
||||
You don’t need boilerplate or complex patterns - state and actions live side by side, fully typed, and ready to use.
|
||||
Start with a basic store to learn the pattern, then expand gradually: use `combine` for cleaner inference, `persist` for storage, and `devtools` for debugging.
|
||||
@ -29,7 +29,7 @@ const hashStorage: StateStorage = {
|
||||
},
|
||||
}
|
||||
|
||||
export const useBoundStore = create(
|
||||
export const useBoundStore = create()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
fishes: 0,
|
||||
@ -102,7 +102,7 @@ const storageOptions = {
|
||||
storage: createJSONStorage<LocalAndUrlStore>(() => persistentStorage),
|
||||
}
|
||||
|
||||
const useLocalAndUrlStore = create(
|
||||
const useLocalAndUrlStore = create()(
|
||||
persist<LocalAndUrlStore>(
|
||||
(set) => ({
|
||||
typesOfFish: [],
|
||||
|
||||
@ -3,6 +3,9 @@ title: Setup with Next.js
|
||||
nav: 17
|
||||
---
|
||||
|
||||
> [!NOTE]
|
||||
> We will be updating this guide soon based on our discussion in https://github.com/pmndrs/zustand/discussions/2740.
|
||||
|
||||
[Next.js](https://nextjs.org) is a popular server-side rendering framework for React that presents
|
||||
some unique challenges for using Zustand properly.
|
||||
Keep in mind that Zustand store is a global
|
||||
|
||||
@ -24,6 +24,7 @@ const nextStateCreatorFn = devtools(stateCreatorFn, devtoolsOptions)
|
||||
- [Usage](#usage)
|
||||
- [Debugging a store](#debugging-a-store)
|
||||
- [Debugging a Slices pattern based store](#debugging-a-slices-pattern-based-store)
|
||||
- [Filtering actions with actionsDenylist](#filtering-actions-with-actionsdenylist)
|
||||
- [Cleanup](#cleanup)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Only one store is displayed](#only-one-store-is-displayed)
|
||||
@ -61,6 +62,9 @@ devtools<T>(stateCreatorFn: StateCreator<T, [], []>, devtoolsOptions?: DevtoolsO
|
||||
- **optional** `anonymousActionType`: Defaults to the inferred action type or `anonymous` if
|
||||
unavailable. A string to use as the action type for anonymous mutations in the Redux DevTools.
|
||||
- **optional** `store`: A custom identifier for the store in the Redux DevTools.
|
||||
- **optional** `actionsDenylist`: A string or array of strings (regex patterns) that specify which
|
||||
actions should be filtered out from Redux DevTools. This option is passed directly to Redux DevTools
|
||||
for filtering. For example, `['secret.*']` will filter out all actions starting with "secret".
|
||||
|
||||
#### Returns
|
||||
|
||||
@ -157,6 +161,61 @@ const useJungleStore = create<JungleStore>()(
|
||||
)
|
||||
```
|
||||
|
||||
### Filtering actions with actionsDenylist
|
||||
|
||||
You can filter out specific actions from Redux DevTools using the `actionsDenylist` option. This is useful for hiding internal or sensitive actions from the DevTools timeline.
|
||||
|
||||
```ts
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
type Store = {
|
||||
user: string | null
|
||||
token: string | null
|
||||
login: (user: string, token: string) => void
|
||||
logout: () => void
|
||||
updateData: () => void
|
||||
}
|
||||
|
||||
const useStore = create<Store>()(
|
||||
devtools(
|
||||
(set) => ({
|
||||
user: null,
|
||||
token: null,
|
||||
login: (user, token) => set({ user, token }, undefined, 'auth/login'),
|
||||
logout: () => set({ user: null, token: null }, undefined, 'auth/logout'),
|
||||
updateData: () =>
|
||||
set({ user: 'updated' }, undefined, 'internal/updateData'),
|
||||
}),
|
||||
{
|
||||
name: 'AuthStore',
|
||||
// Filter out actions matching these regex patterns
|
||||
actionsDenylist: ['internal/.*'], // Hides all 'internal/*' actions
|
||||
},
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
You can also use a single regex string:
|
||||
|
||||
```ts
|
||||
const useStore = create<Store>()(
|
||||
devtools(
|
||||
(set) => ({
|
||||
// ... state and actions
|
||||
}),
|
||||
{
|
||||
name: 'MyStore',
|
||||
actionsDenylist: 'secret.*', // Hides all actions starting with 'secret'
|
||||
},
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> The `actionsDenylist` option uses regex pattern matching and is handled directly by Redux DevTools Extension.
|
||||
> All actions are still sent to DevTools, but matching actions are filtered from the display.
|
||||
|
||||
### Cleanup
|
||||
|
||||
When a store is no longer needed, you can clean up the Redux DevTools connection by calling the `cleanup` method on the store:
|
||||
|
||||
@ -10,14 +10,14 @@ import tseslint from 'typescript-eslint'
|
||||
|
||||
export default defineConfig(
|
||||
{
|
||||
ignores: ['dist/', 'examples/'],
|
||||
ignores: ['dist/', 'examples/', 'website/'],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
importPlugin.flatConfigs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
react.configs.flat.recommended,
|
||||
react.configs.flat['jsx-runtime'],
|
||||
reactHooks.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
{
|
||||
settings: {
|
||||
react: {
|
||||
@ -71,7 +71,6 @@ export default defineConfig(
|
||||
'error',
|
||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
||||
],
|
||||
'react-hooks/react-compiler': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -81,7 +80,6 @@ export default defineConfig(
|
||||
...vitest.configs.recommended,
|
||||
rules: {
|
||||
'import/extensions': ['error', 'never'],
|
||||
'testing-library/no-node-access': 'off',
|
||||
'vitest/consistent-test-it': [
|
||||
'error',
|
||||
{ fn: 'it', withinDescribe: 'it' },
|
||||
|
||||
46
package.json
46
package.json
@ -114,46 +114,46 @@
|
||||
"url": "https://github.com/pmndrs/zustand/issues"
|
||||
},
|
||||
"homepage": "https://github.com/pmndrs/zustand",
|
||||
"packageManager": "pnpm@10.15.0",
|
||||
"packageManager": "pnpm@10.18.3",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@eslint/js": "^9.38.0",
|
||||
"@redux-devtools/extension": "^3.3.0",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-replace": "^6.0.2",
|
||||
"@rollup/plugin-typescript": "12.1.4",
|
||||
"@testing-library/jest-dom": "^6.7.0",
|
||||
"@rollup/plugin-alias": "^6.0.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@rollup/plugin-replace": "^6.0.3",
|
||||
"@rollup/plugin-typescript": "12.3.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@types/node": "^24.9.2",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@types/use-sync-external-store": "^1.5.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vitest/eslint-plugin": "^1.3.4",
|
||||
"@vitest/eslint-plugin": "^1.3.26",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"esbuild": "^0.25.9",
|
||||
"eslint": "9.33.0",
|
||||
"esbuild": "^0.25.11",
|
||||
"eslint": "9.38.0",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jest-dom": "^5.5.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "6.0.0-rc.1",
|
||||
"eslint-plugin-testing-library": "^7.6.6",
|
||||
"immer": "^10.1.1",
|
||||
"jsdom": "^26.1.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-testing-library": "^7.13.3",
|
||||
"immer": "^10.2.0",
|
||||
"jsdom": "^27.0.1",
|
||||
"json": "^11.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"redux": "^5.0.1",
|
||||
"rollup": "^4.46.3",
|
||||
"rollup": "^4.52.5",
|
||||
"rollup-plugin-esbuild": "^6.2.1",
|
||||
"shelljs": "^0.10.0",
|
||||
"shx": "^0.4.0",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.43.0",
|
||||
"use-sync-external-store": "^1.5.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.46.2",
|
||||
"use-sync-external-store": "^1.6.0",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
1906
pnpm-lock.yaml
generated
1906
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- .
|
||||
minimumReleaseAge: 1440
|
||||
@ -645,6 +645,7 @@ it('ensures the correct subscriber is removed on unmount', async () => {
|
||||
function Component() {
|
||||
const [Counter, setCounter] = useState(() => CountWithInitialIncrement)
|
||||
useLayoutEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setCounter(() => Count)
|
||||
}, [])
|
||||
return (
|
||||
|
||||
@ -218,7 +218,7 @@ describe('When state changes with automatic setter inferring...', () => {
|
||||
api.getState().setCount(10)
|
||||
const [connection] = getNamedConnectionApis(options.name)
|
||||
expect(connection.send).toHaveBeenLastCalledWith(
|
||||
{ type: 'Object.setCount' },
|
||||
{ type: expect.stringMatching(/^(Object\.setCount|anonymous)$/) },
|
||||
{ count: 10, setCount: expect.any(Function) },
|
||||
)
|
||||
})
|
||||
@ -669,25 +669,25 @@ describe('different envs', () => {
|
||||
})
|
||||
|
||||
it('works in non-browser env', async () => {
|
||||
const originalWindow = global.window
|
||||
global.window = undefined as any
|
||||
const originalWindow = globalThis.window
|
||||
globalThis.window = undefined as any
|
||||
|
||||
expect(() => {
|
||||
createStore(devtools(() => ({ count: 0 }), { enabled: true }))
|
||||
}).not.toThrow()
|
||||
|
||||
global.window = originalWindow
|
||||
globalThis.window = originalWindow
|
||||
})
|
||||
|
||||
it('works in react native env', async () => {
|
||||
const originalWindow = global.window
|
||||
global.window = {} as any
|
||||
const originalWindow = globalThis.window
|
||||
globalThis.window = {} as any
|
||||
|
||||
expect(() => {
|
||||
createStore(devtools(() => ({ count: 0 }), { enabled: true }))
|
||||
}).not.toThrow()
|
||||
|
||||
global.window = originalWindow
|
||||
globalThis.window = originalWindow
|
||||
})
|
||||
})
|
||||
|
||||
@ -2520,3 +2520,23 @@ describe('cleanup', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('actionsDenylist', () => {
|
||||
it('should pass actionsDenylist option to Redux DevTools', async () => {
|
||||
const options = {
|
||||
name: 'test-filter',
|
||||
enabled: true,
|
||||
actionsDenylist: ['secret.*'],
|
||||
}
|
||||
|
||||
createStore(devtools(() => ({ count: 0 }), options))
|
||||
|
||||
// Verify that actionsDenylist was passed to the connect call
|
||||
const extensionConnector = (window as any).__REDUX_DEVTOOLS_EXTENSION__
|
||||
expect(extensionConnector.connect).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
actionsDenylist: ['secret.*'],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
/* eslint @typescript-eslint/no-unused-expressions: off */ // FIXME
|
||||
/* eslint react-hooks/react-compiler: off */
|
||||
|
||||
import { describe, expect, expectTypeOf, it } from 'vitest'
|
||||
import { create } from 'zustand'
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/// <reference types="node" />
|
||||
|
||||
import { StrictMode, useEffect } from 'react'
|
||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/// <reference types="node" />
|
||||
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { create } from 'zustand'
|
||||
import { createJSONStorage, persist } from 'zustand/middleware'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user