mirror of
https://github.com/pmndrs/zustand.git
synced 2025-12-08 19:45:52 +00:00
Compare commits
4 Commits
b5845a9daa
...
c61999bacd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c61999bacd | ||
|
|
747e97f334 | ||
|
|
295bf970a3 | ||
|
|
73b91d3700 |
@ -428,7 +428,7 @@ interface FishSlice {
|
||||
|
||||
interface SharedSlice {
|
||||
addBoth: () => void
|
||||
getBoth: () => void
|
||||
getBoth: () => number
|
||||
}
|
||||
|
||||
const createBearSlice: StateCreator<
|
||||
|
||||
@ -72,4 +72,5 @@ This can be done using third-party libraries created by the community.
|
||||
- [zustand-xs](https://github.com/zustandjs/zustand-xs) — XState/store compabile middleware for Zustand
|
||||
- [zustand-yjs](https://github.com/tandem-pt/zustand-yjs) — Zustand stores for Yjs structures.
|
||||
- [zusteller](https://github.com/timkindberg/zusteller) — Your global state savior. "Just hooks" + Zustand.
|
||||
- [zustorm](https://github.com/mooalot/zustorm) — A simple and powerful form library for Zustand.
|
||||
- [zusty](https://github.com/oslabs-beta/Zusty) — Zustand tool to assist debugging with time travel, action logs, state snapshots, store view, render time metrics and state component tree.
|
||||
|
||||
@ -82,7 +82,6 @@ export default tseslint.config(
|
||||
'import/extensions': ['error', 'never'],
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'testing-library/no-node-access': 'off',
|
||||
'vitest/expect-expect': 'off',
|
||||
'vitest/consistent-test-it': [
|
||||
'error',
|
||||
{ fn: 'it', withinDescribe: 'it' },
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Mesh, PlaneGeometry, Group, Vector3, MathUtils } from 'three'
|
||||
import { memo, useRef, useState, useLayoutEffect } from 'react'
|
||||
import { useRef, useState, useLayoutEffect } from 'react'
|
||||
import { createRoot, events, extend, useFrame } from '@react-three/fiber'
|
||||
import { Plane, useAspect, useTexture } from '@react-three/drei'
|
||||
import {
|
||||
|
||||
@ -64,7 +64,7 @@ it('uses the store with no args', async () => {
|
||||
</>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 1')
|
||||
expect(await screen.findByText('count: 1')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('uses the store with selectors', async () => {
|
||||
@ -86,7 +86,7 @@ it('uses the store with selectors', async () => {
|
||||
</>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 1')
|
||||
expect(await screen.findByText('count: 1')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('uses the store with a selector and equality checker', async () => {
|
||||
@ -116,15 +116,21 @@ it('uses the store with a selector and equality checker', async () => {
|
||||
</>,
|
||||
)
|
||||
|
||||
await screen.findByText('renderCount: 1, value: 0')
|
||||
expect(
|
||||
await screen.findByText('renderCount: 1, value: 0'),
|
||||
).toBeInTheDocument()
|
||||
|
||||
// This will not cause a re-render.
|
||||
act(() => setState({ item: { value: 1 } }))
|
||||
await screen.findByText('renderCount: 1, value: 0')
|
||||
expect(
|
||||
await screen.findByText('renderCount: 1, value: 0'),
|
||||
).toBeInTheDocument()
|
||||
|
||||
// This will cause a re-render.
|
||||
act(() => setState({ item: { value: 2 } }))
|
||||
await screen.findByText('renderCount: 2, value: 2')
|
||||
expect(
|
||||
await screen.findByText('renderCount: 2, value: 2'),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('only re-renders if selected state has changed', async () => {
|
||||
@ -156,7 +162,7 @@ it('only re-renders if selected state has changed', async () => {
|
||||
|
||||
fireEvent.click(screen.getByText('button'))
|
||||
|
||||
await screen.findByText('count: 1')
|
||||
expect(await screen.findByText('count: 1')).toBeInTheDocument()
|
||||
|
||||
expect(counterRenderCount).toBe(2)
|
||||
expect(controlRenderCount).toBe(1)
|
||||
@ -185,7 +191,7 @@ it('can batch updates', async () => {
|
||||
</>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 2')
|
||||
expect(await screen.findByText('count: 2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('can update the selector', async () => {
|
||||
@ -205,14 +211,14 @@ it('can update the selector', async () => {
|
||||
<Component selector={(s) => s.one} />
|
||||
</StrictMode>,
|
||||
)
|
||||
await screen.findByText('one')
|
||||
expect(await screen.findByText('one')).toBeInTheDocument()
|
||||
|
||||
rerender(
|
||||
<StrictMode>
|
||||
<Component selector={(s) => s.two} />
|
||||
</StrictMode>,
|
||||
)
|
||||
await screen.findByText('two')
|
||||
expect(await screen.findByText('two')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('can update the equality checker', async () => {
|
||||
@ -244,7 +250,9 @@ it('can update the equality checker', async () => {
|
||||
|
||||
// This will cause a re-render due to the equality checker.
|
||||
act(() => setState({ value: 0 }))
|
||||
await screen.findByText('renderCount: 2, value: 0')
|
||||
expect(
|
||||
await screen.findByText('renderCount: 2, value: 0'),
|
||||
).toBeInTheDocument()
|
||||
|
||||
// Set an equality checker that always returns true to never re-render.
|
||||
rerender(
|
||||
@ -255,7 +263,9 @@ it('can update the equality checker', async () => {
|
||||
|
||||
// This will NOT cause a re-render due to the equality checker.
|
||||
act(() => setState({ value: 1 }))
|
||||
await screen.findByText('renderCount: 3, value: 0')
|
||||
expect(
|
||||
await screen.findByText('renderCount: 3, value: 0'),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('can call useBoundStore with progressively more arguments', async () => {
|
||||
@ -287,7 +297,9 @@ it('can call useBoundStore with progressively more arguments', async () => {
|
||||
<Component />
|
||||
</>,
|
||||
)
|
||||
await screen.findByText('renderCount: 1, value: {"value":0}')
|
||||
expect(
|
||||
await screen.findByText('renderCount: 1, value: {"value":0}'),
|
||||
).toBeInTheDocument()
|
||||
|
||||
// Render with selector.
|
||||
rerender(
|
||||
@ -295,7 +307,9 @@ it('can call useBoundStore with progressively more arguments', async () => {
|
||||
<Component selector={(s) => s.value} />
|
||||
</>,
|
||||
)
|
||||
await screen.findByText('renderCount: 2, value: 0')
|
||||
expect(
|
||||
await screen.findByText('renderCount: 2, value: 0'),
|
||||
).toBeInTheDocument()
|
||||
|
||||
// Render with selector and equality checker.
|
||||
rerender(
|
||||
@ -309,10 +323,14 @@ it('can call useBoundStore with progressively more arguments', async () => {
|
||||
|
||||
// Should not cause a re-render because new value is less than previous.
|
||||
act(() => setState({ value: -1 }))
|
||||
await screen.findByText('renderCount: 3, value: 0')
|
||||
expect(
|
||||
await screen.findByText('renderCount: 3, value: 0'),
|
||||
).toBeInTheDocument()
|
||||
|
||||
act(() => setState({ value: 1 }))
|
||||
await screen.findByText('renderCount: 4, value: 1')
|
||||
expect(
|
||||
await screen.findByText('renderCount: 4, value: 1'),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('can throw an error in selector', async () => {
|
||||
@ -355,12 +373,12 @@ it('can throw an error in selector', async () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('no error')
|
||||
expect(await screen.findByText('no error')).toBeInTheDocument()
|
||||
|
||||
act(() => {
|
||||
setState({ value: 123 })
|
||||
})
|
||||
await screen.findByText('errored')
|
||||
expect(await screen.findByText('errored')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('can throw an error in equality checker', async () => {
|
||||
@ -404,12 +422,12 @@ it('can throw an error in equality checker', async () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('no error')
|
||||
expect(await screen.findByText('no error')).toBeInTheDocument()
|
||||
|
||||
act(() => {
|
||||
setState({ value: 123 })
|
||||
})
|
||||
await screen.findByText('errored')
|
||||
expect(await screen.findByText('errored')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('can get the store', () => {
|
||||
@ -499,17 +517,17 @@ it('only calls selectors when necessary with static selector', async () => {
|
||||
<Component />
|
||||
</>,
|
||||
)
|
||||
await screen.findByText('static: 1')
|
||||
expect(await screen.findByText('static: 1')).toBeInTheDocument()
|
||||
|
||||
rerender(
|
||||
<>
|
||||
<Component />
|
||||
</>,
|
||||
)
|
||||
await screen.findByText('static: 1')
|
||||
expect(await screen.findByText('static: 1')).toBeInTheDocument()
|
||||
|
||||
act(() => setState({ a: 1, b: 1 }))
|
||||
await screen.findByText('static: 2')
|
||||
expect(await screen.findByText('static: 2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('only calls selectors when necessary (traditional)', async () => {
|
||||
@ -540,20 +558,20 @@ it('only calls selectors when necessary (traditional)', async () => {
|
||||
<Component />
|
||||
</>,
|
||||
)
|
||||
await screen.findByText('inline: 1')
|
||||
await screen.findByText('static: 1')
|
||||
expect(await screen.findByText('inline: 1')).toBeInTheDocument()
|
||||
expect(await screen.findByText('static: 1')).toBeInTheDocument()
|
||||
|
||||
rerender(
|
||||
<>
|
||||
<Component />
|
||||
</>,
|
||||
)
|
||||
await screen.findByText('inline: 2')
|
||||
await screen.findByText('static: 1')
|
||||
expect(await screen.findByText('inline: 2')).toBeInTheDocument()
|
||||
expect(await screen.findByText('static: 1')).toBeInTheDocument()
|
||||
|
||||
act(() => setState({ a: 1, b: 1 }))
|
||||
await screen.findByText('inline: 4')
|
||||
await screen.findByText('static: 2')
|
||||
expect(await screen.findByText('inline: 4')).toBeInTheDocument()
|
||||
expect(await screen.findByText('static: 2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('ensures parent components subscribe before children', async () => {
|
||||
@ -602,7 +620,7 @@ it('ensures parent components subscribe before children', async () => {
|
||||
|
||||
fireEvent.click(screen.getByText('change state'))
|
||||
|
||||
await screen.findByText('child 3')
|
||||
expect(await screen.findByText('child 3')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// https://github.com/pmndrs/zustand/issues/84
|
||||
@ -715,10 +733,10 @@ it('works with non-object state', async () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 1')
|
||||
expect(await screen.findByText('count: 1')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByText('button'))
|
||||
await screen.findByText('count: 2')
|
||||
expect(await screen.findByText('count: 2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('works with "undefined" state', async () => {
|
||||
@ -735,5 +753,5 @@ it('works with "undefined" state', async () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('str: undefined')
|
||||
expect(await screen.findByText('str: undefined')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -81,8 +81,10 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 0, name: empty')
|
||||
await screen.findByText('count: 42, name: test-storage')
|
||||
expect(await screen.findByText('count: 0, name: empty')).toBeInTheDocument()
|
||||
expect(
|
||||
await screen.findByText('count: 42, name: test-storage'),
|
||||
).toBeInTheDocument()
|
||||
expect(onRehydrateStorageSpy).toBeCalledWith(
|
||||
{ count: 42, name: 'test-storage' },
|
||||
undefined,
|
||||
@ -119,7 +121,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 0')
|
||||
expect(await screen.findByText('count: 0')).toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(onRehydrateStorageSpy).toBeCalledWith(
|
||||
undefined,
|
||||
@ -157,14 +159,14 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 0')
|
||||
expect(await screen.findByText('count: 0')).toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(onRehydrateStorageSpy).toBeCalledWith({ count: 0 }, undefined)
|
||||
})
|
||||
|
||||
// Write something to the store
|
||||
act(() => useBoundStore.setState({ count: 42 }))
|
||||
await screen.findByText('count: 42')
|
||||
expect(await screen.findByText('count: 42')).toBeInTheDocument()
|
||||
expect(setItemSpy).toBeCalledWith(
|
||||
'test-storage',
|
||||
JSON.stringify({ state: { count: 42 }, version: 0 }),
|
||||
@ -187,7 +189,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 42')
|
||||
expect(await screen.findByText('count: 42')).toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(onRehydrateStorageSpy2).toBeCalledWith({ count: 42 }, undefined)
|
||||
})
|
||||
@ -229,8 +231,8 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 0')
|
||||
await screen.findByText('count: 99')
|
||||
expect(await screen.findByText('count: 0')).toBeInTheDocument()
|
||||
expect(await screen.findByText('count: 99')).toBeInTheDocument()
|
||||
expect(migrateSpy).toBeCalledWith({ count: 42 }, 12)
|
||||
expect(setItemSpy).toBeCalledWith(
|
||||
'test-storage',
|
||||
@ -291,8 +293,8 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 42')
|
||||
await screen.findByText('name: test')
|
||||
expect(await screen.findByText('count: 42')).toBeInTheDocument()
|
||||
expect(await screen.findByText('name: test')).toBeInTheDocument()
|
||||
|
||||
expect(useBoundStore.getState()).toEqual(
|
||||
expect.objectContaining({
|
||||
@ -335,7 +337,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 0')
|
||||
expect(await screen.findByText('count: 0')).toBeInTheDocument()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
@ -383,7 +385,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 0')
|
||||
expect(await screen.findByText('count: 0')).toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(onRehydrateStorageSpy).toBeCalledWith(
|
||||
undefined,
|
||||
@ -432,7 +434,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 1')
|
||||
expect(await screen.findByText('count: 1')).toBeInTheDocument()
|
||||
|
||||
// The 'onRehydrateStorage' spy is invoked prior to rehydration, so it should
|
||||
// be passed the default state.
|
||||
@ -475,7 +477,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 0')
|
||||
expect(await screen.findByText('count: 0')).toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(onRehydrateStorageSpy).toBeCalledWith(
|
||||
{ count: 1, unstorableMethod },
|
||||
@ -527,7 +529,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 1')
|
||||
expect(await screen.findByText('count: 1')).toBeInTheDocument()
|
||||
expect(useBoundStore.getState()).toEqual({
|
||||
count: 1,
|
||||
actions: {
|
||||
@ -566,7 +568,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 1')
|
||||
expect(await screen.findByText('count: 1')).toBeInTheDocument()
|
||||
expect(useBoundStore.getState()).toEqual({
|
||||
count: 1,
|
||||
})
|
||||
@ -701,7 +703,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('count: 2')
|
||||
expect(await screen.findByText('count: 2')).toBeInTheDocument()
|
||||
expect(useBoundStore.getState().count).toEqual(2)
|
||||
})
|
||||
|
||||
@ -742,7 +744,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('map: bar')
|
||||
expect(await screen.findByText('map: bar')).toBeInTheDocument()
|
||||
expect(onRehydrateStorageSpy).toBeCalledWith(
|
||||
{ map: new Map([['foo', 'bar']]) },
|
||||
undefined,
|
||||
@ -779,7 +781,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('map-content:')
|
||||
expect(await screen.findByText('map-content:')).toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(onRehydrateStorageSpy).toBeCalledWith({ map }, undefined)
|
||||
})
|
||||
@ -787,7 +789,7 @@ describe('persist middleware with async configuration', () => {
|
||||
// Write something to the store
|
||||
const updatedMap = new Map(map).set('foo', 'bar')
|
||||
act(() => useBoundStore.setState({ map: updatedMap }))
|
||||
await screen.findByText('map-content: bar')
|
||||
expect(await screen.findByText('map-content: bar')).toBeInTheDocument()
|
||||
|
||||
expect(setItemSpy).toBeCalledWith(
|
||||
'test-storage',
|
||||
@ -814,7 +816,7 @@ describe('persist middleware with async configuration', () => {
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
await screen.findByText('map-content: bar')
|
||||
expect(await screen.findByText('map-content: bar')).toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(onRehydrateStorageSpy2).toBeCalledWith(
|
||||
{ map: updatedMap },
|
||||
|
||||
@ -42,7 +42,7 @@ it('can use exposed types', () => {
|
||||
num: 1,
|
||||
numGet: () => get().num,
|
||||
numGetState: () => {
|
||||
// TypeScript can't get the type of storeApi when it trys to enforce the signature of numGetState.
|
||||
// TypeScript can't get the type of storeApi when it tries to enforce the signature of numGetState.
|
||||
// Need to explicitly state the type of storeApi.getState().num or storeApi type will be type 'any'.
|
||||
const result: number = storeApi.getState().num
|
||||
return result
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user