Skip to content

Commit e247220

Browse files
dai-shicharkourdbritto-devonlinedYonom
authored
v5 (#2138)
* prepare for the next major version * [v5] breaking: drop default exports (#2238) * fix: drop default exports for v5 * chore: remove default from cjs build * refactor: export shallow in v5 * fix: remove `addModuleExport` option for cjs. * [v5] breaking: drop deprecated features (#2235) * fix: remove deprecated v4 features * chore(build): remove context * docs(typescript): remove deprecated equals api * docs(persist): remove old persist api * chore: run yarn prettier on typescript docs * Discard changes to docs/guides/typescript.md * Discard changes to docs/integrations/persisting-store-data.md * Discard changes to tests/shallow.test.tsx * Discard changes to tests/vanilla/subscribe.test.tsx * [v5] breaking: make React 18 as minimal requirement (#2236) * fix: update package.json to require react 18+ * chore: update github actions to test on react 18+ * chore: remove devtools-skip hack from actions * chore(test): remove CI-SKIP from devtools tests * [v5] breaking: make use-sync-external-store an optional peer dependency (#2237) * chore: make use-sync-external-store optional peerDep * fix: use correct versions in package.json * [v5] breaking: require TypeScript 4.5 and update tests (#2257) * breaking(types): TS requirement * wip: latest only * wip: latest only 2 * drop ts <4.4 * wip: do not skip lib checkes * use latest node types * drop ts 4.4 * [v5]: drop "module" condition (#2270) * Update package json in order to remove module * Update rollup config in order to remove module config * Update patch esm script * Update package json to general exports and update node version (#2272) * [v5]: drop UMD/SystemJS builds (#2287) * Update rollup config in order to drop system js and umd builds * Update packages * Clean up files * Update rollup config * Update gh workflows * Minor fixes * Minor fixes * Minor fixes * Minor fixes * Testing * Minor changes * Minor fixes * remove `WithReact` type (#2300) * 5.0.0-alpha.0 * [v5]: do not depend on use-sync-external-store (#2301) * [v5]: do not depend on use-sync-external-store * memo get(server)snapshot * 5.0.0-alpha.1 * [v5]: refactor useMemoSelector (#2302) * [v5]: refactor useMemoSelector * add a test * Revert "[v5]: refactor useMemoSelector" This reverts commit b3c8b15. * Revert "Revert "[v5]: refactor useMemoSelector"" This reverts commit 3c47301. * [v5]: separate react entry point (#2303) * 5.0.0-alpha.2 * 5.0.0-alpha.3 * refactor: Switch to Object.hasOwn (#2365) * [v5] drop es5 (#2380) * update yarn lock * 5.0.0-alpha.4 * [v5]: follow React "standard" way with breaking behavioral change (#2395) * [v5]: follow React "standard" way with breaking behavioral change * add test * 5.0.0-alpha.5 * [v5] Rewrite shallow to support iterables (#2427) * [v5] fix rollup config for cjs (#2433) * 5.0.0-alpha.6 * no production build test * recover types that are dropped in #2462 * remove unused replacement * [v5] Remove Devtools warning (#2466) * chore: remove devtools extension warning * docs: add devtools link to readme * chore: remove unused test * chrome: remove unused tests * chore: remove unused test * Revert "chore: remove unused test" This reverts commit 0fa2a75. * update test name * update pnpm lock * fix merge main * add migration guide * fix typos * 5.0.0-beta.0 * update migration doc * fix merge main * fix merge main (prettier) * 5.0.0-beta.1 * fix(types)!: require complete state if `setState`'s `replace` flag is set (#2580) * fix(types): require complete state if `setState`'s `replace` flag is set * switch to variant 2 * fix type errors * update setState types for devtools and immer * make devtools setState non-generic * add migration guide * merge migration guides * run prettier * Update tests/middlewareTypes.test.tsx --------- Co-authored-by: Daishi Kato <[email protected]> Co-authored-by: daishi <[email protected]> * 5.0.0-beta.2 * move v5 migration doc * fix ci * missing commmit * remove unused rule exclusion * comment about react compiler * revert eslint config --------- Co-authored-by: Charles Kornoelje <[email protected]> Co-authored-by: Danilo Britto <[email protected]> Co-authored-by: Ekin Dursun <[email protected]> Co-authored-by: Simon Farshid <[email protected]>
1 parent 53e5a2a commit e247220

29 files changed

+523
-2560
lines changed

.eslintrc.json

+1-5
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,7 @@
4545
"@typescript-eslint/explicit-module-boundary-types": "off",
4646
"@typescript-eslint/no-unused-vars": [
4747
"warn",
48-
{
49-
"argsIgnorePattern": "^_",
50-
"varsIgnorePattern": "^_",
51-
"caughtErrorsIgnorePattern": "^_"
52-
}
48+
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
5349
],
5450
"@typescript-eslint/no-use-before-define": "off",
5551
"@typescript-eslint/no-empty-function": "off",

.github/workflows/test-multiple-builds.yml

+2-19
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ jobs:
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
build: [cjs, esm, umd]
16-
env: [development, production]
15+
build: [cjs, esm]
16+
env: [development] # [development, production]
1717
steps:
1818
- uses: actions/checkout@v4
1919
- uses: pnpm/action-setup@v4
@@ -24,10 +24,6 @@ jobs:
2424
cache-dependency-path: '**/pnpm-lock.yaml'
2525
- run: pnpm install --frozen-lockfile
2626
- run: pnpm build
27-
- name: Use React 17 for production test
28-
if: ${{ matrix.env == 'production' }}
29-
run: |
30-
3127
- name: Patch for DEV-ONLY
3228
if: ${{ matrix.env == 'development' }}
3329
run: |
@@ -42,26 +38,13 @@ jobs:
4238
if: ${{ matrix.build == 'cjs' }}
4339
run: |
4440
sed -i~ "s/resolve('\.\/src\(.*\)\.ts')/resolve('\.\/dist\1.js')/" vitest.config.ts
45-
sed -i~ "s/module.exports.createStore = vanilla.createStore;//" dist/index.js
4641
- name: Patch for ESM
4742
if: ${{ matrix.build == 'esm' }}
4843
run: |
4944
sed -i~ "s/resolve('\.\/src\(.*\)\.ts')/resolve('\.\/dist\/esm\1.mjs')/" vitest.config.ts
5045
sed -i~ "1s/^/import.meta.env=import.meta.env||{};import.meta.env.MODE='${NODE_ENV}';/" tests/*.tsx
5146
env:
5247
NODE_ENV: ${{ matrix.env }}
53-
- name: Patch for UMD
54-
if: ${{ matrix.build == 'umd' }}
55-
run: |
56-
sed -i~ "s/resolve('\.\/src\(.*\)\.ts')/resolve('\.\/dist\/umd\1.${NODE_ENV}.js')/" vitest.config.ts
57-
env:
58-
NODE_ENV: ${{ matrix.env }}
59-
- name: Patch for SystemJS
60-
if: ${{ matrix.build == 'system' }}
61-
run: |
62-
sed -i~ "s/resolve('\.\/src\(.*\)\.ts')/resolve('\.\/dist\/system\1.${NODE_ENV}.js')/" vitest.config.ts
63-
env:
64-
NODE_ENV: ${{ matrix.env }}
6548
- name: Test ${{ matrix.build }} ${{ matrix.env }}
6649
run: |
6750
pnpm test:spec

.github/workflows/test-multiple-versions.yml

-30
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,13 @@ jobs:
2727
fail-fast: false
2828
matrix:
2929
react:
30-
- 16.8.0
31-
- 17.0.0
3230
- 18.0.0
3331
- 18.1.0
3432
- 18.2.0
3533
- 18.3.1
3634
- 19.0.0-rc.0
3735
- 19.0.0-rc-49496d49-20240814
3836
- 0.0.0-experimental-49496d49-20240814
39-
devtools-skip:
40-
- CI-MATRIX-NOSKIP
41-
include:
42-
- devtools-skip: CI-MATRIX-[2345]
43-
react: 16.8.0
44-
- devtools-skip: CI-MATRIX-[1345]
45-
react: 16.8.0
46-
- devtools-skip: CI-MATRIX-[1245]
47-
react: 16.8.0
48-
- devtools-skip: CI-MATRIX-[1235]
49-
react: 16.8.0
50-
- devtools-skip: CI-MATRIX-[1234]
51-
react: 16.8.0
52-
exclude:
53-
- devtools-skip: CI-MATRIX-NOSKIP
54-
react: 16.8.0
5537
steps:
5638
- uses: actions/checkout@v4
5739
- uses: pnpm/action-setup@v4
@@ -61,18 +43,6 @@ jobs:
6143
cache: 'pnpm'
6244
cache-dependency-path: '**/pnpm-lock.yaml'
6345
- run: pnpm install --frozen-lockfile
64-
- name: Install legacy testing-library
65-
if: ${{ startsWith(matrix.react, '16.') || startsWith(matrix.react, '17.') }}
66-
run: pnpm add -D @testing-library/[email protected]
67-
- name: Patch for React 16
68-
if: ${{ startsWith(matrix.react, '16.') }}
69-
run: |
70-
sed -i~ '1s/^/import React from "react";/' tests/*.tsx
71-
sed -i~ 's/"jsx": "react-jsx"/"jsx": "react"/' tsconfig.json
72-
sed -i~ 's/import\.meta\.env[?]\.MODE/"DEVELOPMENT".toLowerCase()/' src/*.ts src/*/*.ts
73-
sed -i~ "s/it('\[${DEVTOOLS_SKIP}\]/it.skip('/" tests/devtools.test.tsx
74-
env:
75-
DEVTOOLS_SKIP: ${{ matrix.devtools-skip }}
7646
- name: Test ${{ matrix.react }} ${{ matrix.devtools-skip }}
7747
run: |
7848
pnpm add -D react@${{ matrix.react }} react-dom@${{ matrix.react }}

.github/workflows/test-old-typescript.yml

-11
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ jobs:
2323
- 4.7.4
2424
- 4.6.4
2525
- 4.5.5
26-
- 4.4.4
27-
- 4.3.5
28-
- 4.2.3
29-
- 4.1.5
3026
steps:
3127
- uses: actions/checkout@v4
3228
- uses: pnpm/action-setup@v4
@@ -43,9 +39,6 @@ jobs:
4339
sed -i~ 's/"verbatimModuleSyntax": true,//' tsconfig.json
4440
- name: Patch for Old TS
4541
run: |
46-
sed -i~ 's/\/\/ @ts-expect-error.*\[LATEST-TS-ONLY\]//' tests/*.tsx
47-
sed -i~ 's/"target":/"skipLibCheck":true,"target":/' tsconfig.json
48-
sed -i~ 's/"exactOptionalPropertyTypes": true,//' tsconfig.json
4942
sed -i~ 's/"moduleResolution": "bundler",/"moduleResolution": "node",/' tsconfig.json
5043
sed -i~ 's/"allowImportingTsExtensions": true,//' tsconfig.json
5144
sed -i~ 's/"zustand": \["\.\/src\/index\.ts"\],/"zustand": [".\/dist\/index.d.ts"],/' tsconfig.json
@@ -55,9 +48,5 @@ jobs:
5548
pnpm add -D @types/[email protected]
5649
- name: Install old TypeScript
5750
run: pnpm add -D typescript@${{ matrix.typescript }}
58-
- name: Patch testing setup for Old TS
59-
if: ${{ matrix.typescript == '4.4.4' || matrix.typescript == '4.3.5' || matrix.typescript == '4.2.3' || matrix.typescript == '4.1.5' }}
60-
run: |
61-
6251
- name: Test ${{ matrix.typescript }}
6352
run: pnpm test:types

babel.config.js

-28
This file was deleted.

docs/guides/typescript.md

+30
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,36 @@ For a usual statically typed language, this is impossible. But thanks to TypeScr
232232

233233
If you are eager to know what the answer is to this particular problem then you can [see it here](#middleware-that-changes-the-store-type).
234234

235+
### Handling Dynamic `replace` Flag
236+
237+
If the value of the `replace` flag is not known at compile time and is determined dynamically, you might face issues. To handle this, you can use a workaround by annotating the `replace` parameter with `as any`:
238+
239+
```ts
240+
const replaceFlag = Math.random() > 0.5
241+
store.setState(partialOrFull, replaceFlag as any)
242+
```
243+
244+
#### Example with `as any` Workaround
245+
246+
```ts
247+
import { create } from 'zustand'
248+
249+
interface BearState {
250+
bears: number
251+
increase: (by: number) => void
252+
}
253+
254+
const useBearStore = create<BearState>()((set) => ({
255+
bears: 0,
256+
increase: (by) => set((state) => ({ bears: state.bears + by })),
257+
}))
258+
259+
const replaceFlag = Math.random() > 0.5
260+
useBearStore.setState({ bears: 5 }, replaceFlag as any) // Using the workaround
261+
```
262+
263+
By following this approach, you can ensure that your code handles dynamic `replace` flags without encountering type issues.
264+
235265
## Common recipes
236266

237267
### Middleware that doesn't change the store type

docs/migrations/migrating-to-v5.md

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
---
2+
title: 'How to Migrate to v5 from v4'
3+
nav: 30
4+
---
5+
6+
# How to Migrate to v5 from v4
7+
8+
We highly recommend to update to the latest version of v4, before migrating to v5. It will show all deprecation warnings without breaking your app.
9+
10+
## Changes in v5
11+
12+
- Drop default exports
13+
- Drop deprecated features
14+
- Make React 18 the minimum required version
15+
- Make use-sync-external-store a peer dependency (required for `createWithEqualityFn` and `useStoreWithEqualityFn` in `zustand/traditional`)
16+
- Make TypeScript 4.5 the minimum required version
17+
- Drop UMD/SystemJS support
18+
- Organize entry points in the package.json
19+
- Drop ES5 support
20+
- Stricter types when setState's replace flag is set
21+
- Other small improvements (technically breaking changes)
22+
23+
## Migration Guide
24+
25+
### Using custom equality functions such as `shallow`
26+
27+
The `create` function in v5 does not support customizing equality function.
28+
29+
If you use custom equality function such as `shallow`,
30+
the easiest migration is to use `createWithEqualityFn`.
31+
32+
```js
33+
// v4
34+
import { create } from 'zustand'
35+
import { shallow } from 'zustand/shallow'
36+
37+
const useCountStore = create((set) => ({
38+
count: 0,
39+
text: 'hello',
40+
// ...
41+
}))
42+
43+
const Component = () => {
44+
const { count, text } = useCountStore(
45+
(state) => ({
46+
count: state.count,
47+
text: state.text,
48+
}),
49+
shallow,
50+
)
51+
// ...
52+
}
53+
```
54+
55+
That can be done with `createWithEqualityFn` in v5:
56+
57+
```bash
58+
npm install use-sync-external-store
59+
```
60+
61+
```js
62+
// v5
63+
import { createWithEqualityFn as create } from 'zustand/traditional'
64+
65+
// The rest is the same as v4
66+
```
67+
68+
Alternatively, for the `shallow` use case, you can use `useShallow` hook:
69+
70+
```js
71+
// v5
72+
import { create } from 'zustand'
73+
import { useShallow } from 'zustand/shallow'
74+
75+
const useCountStore = create((set) => ({
76+
count: 0,
77+
text: 'hello',
78+
// ...
79+
}))
80+
81+
const Component = () => {
82+
const { count, text } = useCountStore(
83+
useShallow((state) => ({
84+
count: state.count,
85+
text: state.text,
86+
})),
87+
)
88+
// ...
89+
}
90+
```
91+
92+
### Requiring stable selector outputs
93+
94+
There is a behavioral change in v5 to match React default behavior.
95+
If a selector returns a new reference, it may cause infinite loops.
96+
97+
For example, this may cause infinite loops.
98+
99+
```js
100+
// v4
101+
const action = useMainStore((state) => {
102+
return state.action ?? () => {}
103+
})
104+
```
105+
106+
The error message will be something like this:
107+
108+
```
109+
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
110+
```
111+
112+
To fix it, make sure the selector function returns a stable reference.
113+
114+
```js
115+
// v5
116+
117+
const FALLBACK_ACTION = () => {}
118+
119+
const action = useMainStore((state) => {
120+
return state.action ?? FALLBACK_ACTION
121+
})
122+
```
123+
124+
Alternatively, if you need v4 behavior, `createWithEqualityFn` will do.
125+
126+
```js
127+
// v5
128+
import { createWithEqualityFn as create } from 'zustand/traditional'
129+
```
130+
131+
### Stricter types when setState's replace flag is set (Typescript only)
132+
133+
```diff
134+
- setState:
135+
- (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean | undefined) => void;
136+
+ setState:
137+
+ (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: false) => void;
138+
+ (state: T | ((state: T) => T), replace: true) => void;
139+
```
140+
141+
If you are not using the `replace` flag, no migration is required.
142+
143+
If you are using the `replace` flag and it's set to `true`, you must provide a complete state object.
144+
This change ensures that `store.setState({}, true)` (which results in an invalid state) is no longer considered valid.
145+
146+
**Examples:**
147+
148+
```ts
149+
// Partial state update (valid)
150+
store.setState({ key: 'value' })
151+
152+
// Complete state replacement (valid)
153+
store.setState({ key: 'value' }, true)
154+
155+
// Incomplete state replacement (invalid)
156+
store.setState({}, true) // Error
157+
```
158+
159+
#### Handling Dynamic `replace` Flag
160+
161+
If the value of the `replace` flag is dynamic and determined at runtime, you might face issues. To handle this, you can use a workaround by annotating the `replace` parameter with `as any`:
162+
163+
```ts
164+
const replaceFlag = Math.random() > 0.5
165+
store.setState(partialOrFull, replaceFlag as any)
166+
```
167+
168+
## Links
169+
170+
- https://github.com/pmndrs/zustand/pull/2138
171+
- https://github.com/pmndrs/zustand/pull/2580

0 commit comments

Comments
 (0)