Skip to content

Commit

Permalink
v5 (#2138)
Browse files Browse the repository at this point in the history
* 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 <dai-shi@users.noreply.github.com>
Co-authored-by: daishi <daishi@axlight.com>

* 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 <33156025+charkour@users.noreply.github.com>
Co-authored-by: Danilo Britto <dbritto.dev@gmail.com>
Co-authored-by: Ekin Dursun <ekindursun@gmail.com>
Co-authored-by: Simon Farshid <simon.farshid@outlook.com>
  • Loading branch information
5 people authored Aug 16, 2024
1 parent 53e5a2a commit e247220
Show file tree
Hide file tree
Showing 29 changed files with 523 additions and 2,560 deletions.
6 changes: 1 addition & 5 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,7 @@
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_"
}
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
],
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-empty-function": "off",
Expand Down
21 changes: 2 additions & 19 deletions .github/workflows/test-multiple-builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ jobs:
strategy:
fail-fast: false
matrix:
build: [cjs, esm, umd]
env: [development, production]
build: [cjs, esm]
env: [development] # [development, production]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand All @@ -24,10 +24,6 @@ jobs:
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Use React 17 for production test
if: ${{ matrix.env == 'production' }}
run: |
pnpm add -D react@17.0.2 react-dom@17.0.2 @testing-library/react@12.1.4
- name: Patch for DEV-ONLY
if: ${{ matrix.env == 'development' }}
run: |
Expand All @@ -42,26 +38,13 @@ jobs:
if: ${{ matrix.build == 'cjs' }}
run: |
sed -i~ "s/resolve('\.\/src\(.*\)\.ts')/resolve('\.\/dist\1.js')/" vitest.config.ts
sed -i~ "s/module.exports.createStore = vanilla.createStore;//" dist/index.js
- name: Patch for ESM
if: ${{ matrix.build == 'esm' }}
run: |
sed -i~ "s/resolve('\.\/src\(.*\)\.ts')/resolve('\.\/dist\/esm\1.mjs')/" vitest.config.ts
sed -i~ "1s/^/import.meta.env=import.meta.env||{};import.meta.env.MODE='${NODE_ENV}';/" tests/*.tsx
env:
NODE_ENV: ${{ matrix.env }}
- name: Patch for UMD
if: ${{ matrix.build == 'umd' }}
run: |
sed -i~ "s/resolve('\.\/src\(.*\)\.ts')/resolve('\.\/dist\/umd\1.${NODE_ENV}.js')/" vitest.config.ts
env:
NODE_ENV: ${{ matrix.env }}
- name: Patch for SystemJS
if: ${{ matrix.build == 'system' }}
run: |
sed -i~ "s/resolve('\.\/src\(.*\)\.ts')/resolve('\.\/dist\/system\1.${NODE_ENV}.js')/" vitest.config.ts
env:
NODE_ENV: ${{ matrix.env }}
- name: Test ${{ matrix.build }} ${{ matrix.env }}
run: |
pnpm test:spec
Expand Down
30 changes: 0 additions & 30 deletions .github/workflows/test-multiple-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,13 @@ jobs:
fail-fast: false
matrix:
react:
- 16.8.0
- 17.0.0
- 18.0.0
- 18.1.0
- 18.2.0
- 18.3.1
- 19.0.0-rc.0
- 19.0.0-rc-49496d49-20240814
- 0.0.0-experimental-49496d49-20240814
devtools-skip:
- CI-MATRIX-NOSKIP
include:
- devtools-skip: CI-MATRIX-[2345]
react: 16.8.0
- devtools-skip: CI-MATRIX-[1345]
react: 16.8.0
- devtools-skip: CI-MATRIX-[1245]
react: 16.8.0
- devtools-skip: CI-MATRIX-[1235]
react: 16.8.0
- devtools-skip: CI-MATRIX-[1234]
react: 16.8.0
exclude:
- devtools-skip: CI-MATRIX-NOSKIP
react: 16.8.0
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand All @@ -61,18 +43,6 @@ jobs:
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --frozen-lockfile
- name: Install legacy testing-library
if: ${{ startsWith(matrix.react, '16.') || startsWith(matrix.react, '17.') }}
run: pnpm add -D @testing-library/react@12.1.4
- name: Patch for React 16
if: ${{ startsWith(matrix.react, '16.') }}
run: |
sed -i~ '1s/^/import React from "react";/' tests/*.tsx
sed -i~ 's/"jsx": "react-jsx"/"jsx": "react"/' tsconfig.json
sed -i~ 's/import\.meta\.env[?]\.MODE/"DEVELOPMENT".toLowerCase()/' src/*.ts src/*/*.ts
sed -i~ "s/it('\[${DEVTOOLS_SKIP}\]/it.skip('/" tests/devtools.test.tsx
env:
DEVTOOLS_SKIP: ${{ matrix.devtools-skip }}
- name: Test ${{ matrix.react }} ${{ matrix.devtools-skip }}
run: |
pnpm add -D react@${{ matrix.react }} react-dom@${{ matrix.react }}
Expand Down
11 changes: 0 additions & 11 deletions .github/workflows/test-old-typescript.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ jobs:
- 4.7.4
- 4.6.4
- 4.5.5
- 4.4.4
- 4.3.5
- 4.2.3
- 4.1.5
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand All @@ -43,9 +39,6 @@ jobs:
sed -i~ 's/"verbatimModuleSyntax": true,//' tsconfig.json
- name: Patch for Old TS
run: |
sed -i~ 's/\/\/ @ts-expect-error.*\[LATEST-TS-ONLY\]//' tests/*.tsx
sed -i~ 's/"target":/"skipLibCheck":true,"target":/' tsconfig.json
sed -i~ 's/"exactOptionalPropertyTypes": true,//' tsconfig.json
sed -i~ 's/"moduleResolution": "bundler",/"moduleResolution": "node",/' tsconfig.json
sed -i~ 's/"allowImportingTsExtensions": true,//' tsconfig.json
sed -i~ 's/"zustand": \["\.\/src\/index\.ts"\],/"zustand": [".\/dist\/index.d.ts"],/' tsconfig.json
Expand All @@ -55,9 +48,5 @@ jobs:
pnpm add -D @types/node@18.13.0
- name: Install old TypeScript
run: pnpm add -D typescript@${{ matrix.typescript }}
- name: Patch testing setup for Old TS
if: ${{ matrix.typescript == '4.4.4' || matrix.typescript == '4.3.5' || matrix.typescript == '4.2.3' || matrix.typescript == '4.1.5' }}
run: |
pnpm add -D vitest@0.33.0 @vitest/coverage-v8@0.33.0 @vitest/ui@0.33.0
- name: Test ${{ matrix.typescript }}
run: pnpm test:types
28 changes: 0 additions & 28 deletions babel.config.js

This file was deleted.

30 changes: 30 additions & 0 deletions docs/guides/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,36 @@ For a usual statically typed language, this is impossible. But thanks to TypeScr

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

### Handling Dynamic `replace` Flag

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`:

```ts
const replaceFlag = Math.random() > 0.5
store.setState(partialOrFull, replaceFlag as any)
```

#### Example with `as any` Workaround

```ts
import { create } from 'zustand'

interface BearState {
bears: number
increase: (by: number) => void
}

const useBearStore = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))

const replaceFlag = Math.random() > 0.5
useBearStore.setState({ bears: 5 }, replaceFlag as any) // Using the workaround
```

By following this approach, you can ensure that your code handles dynamic `replace` flags without encountering type issues.

## Common recipes

### Middleware that doesn't change the store type
Expand Down
171 changes: 171 additions & 0 deletions docs/migrations/migrating-to-v5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
---
title: 'How to Migrate to v5 from v4'
nav: 30
---

# How to Migrate to v5 from v4

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.

## Changes in v5

- Drop default exports
- Drop deprecated features
- Make React 18 the minimum required version
- Make use-sync-external-store a peer dependency (required for `createWithEqualityFn` and `useStoreWithEqualityFn` in `zustand/traditional`)
- Make TypeScript 4.5 the minimum required version
- Drop UMD/SystemJS support
- Organize entry points in the package.json
- Drop ES5 support
- Stricter types when setState's replace flag is set
- Other small improvements (technically breaking changes)

## Migration Guide

### Using custom equality functions such as `shallow`

The `create` function in v5 does not support customizing equality function.

If you use custom equality function such as `shallow`,
the easiest migration is to use `createWithEqualityFn`.

```js
// v4
import { create } from 'zustand'
import { shallow } from 'zustand/shallow'

const useCountStore = create((set) => ({
count: 0,
text: 'hello',
// ...
}))

const Component = () => {
const { count, text } = useCountStore(
(state) => ({
count: state.count,
text: state.text,
}),
shallow,
)
// ...
}
```

That can be done with `createWithEqualityFn` in v5:

```bash
npm install use-sync-external-store
```

```js
// v5
import { createWithEqualityFn as create } from 'zustand/traditional'

// The rest is the same as v4
```

Alternatively, for the `shallow` use case, you can use `useShallow` hook:

```js
// v5
import { create } from 'zustand'
import { useShallow } from 'zustand/shallow'

const useCountStore = create((set) => ({
count: 0,
text: 'hello',
// ...
}))

const Component = () => {
const { count, text } = useCountStore(
useShallow((state) => ({
count: state.count,
text: state.text,
})),
)
// ...
}
```

### Requiring stable selector outputs

There is a behavioral change in v5 to match React default behavior.
If a selector returns a new reference, it may cause infinite loops.

For example, this may cause infinite loops.

```js
// v4
const action = useMainStore((state) => {
return state.action ?? () => {}
})
```
The error message will be something like this:
```
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.
```
To fix it, make sure the selector function returns a stable reference.
```js
// v5

const FALLBACK_ACTION = () => {}

const action = useMainStore((state) => {
return state.action ?? FALLBACK_ACTION
})
```
Alternatively, if you need v4 behavior, `createWithEqualityFn` will do.
```js
// v5
import { createWithEqualityFn as create } from 'zustand/traditional'
```
### Stricter types when setState's replace flag is set (Typescript only)
```diff
- setState:
- (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean | undefined) => void;
+ setState:
+ (partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: false) => void;
+ (state: T | ((state: T) => T), replace: true) => void;
```
If you are not using the `replace` flag, no migration is required.
If you are using the `replace` flag and it's set to `true`, you must provide a complete state object.
This change ensures that `store.setState({}, true)` (which results in an invalid state) is no longer considered valid.
**Examples:**
```ts
// Partial state update (valid)
store.setState({ key: 'value' })

// Complete state replacement (valid)
store.setState({ key: 'value' }, true)

// Incomplete state replacement (invalid)
store.setState({}, true) // Error
```
#### Handling Dynamic `replace` Flag
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`:
```ts
const replaceFlag = Math.random() > 0.5
store.setState(partialOrFull, replaceFlag as any)
```
## Links
- https://github.com/pmndrs/zustand/pull/2138
- https://github.com/pmndrs/zustand/pull/2580
Loading

0 comments on commit e247220

Please sign in to comment.