Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pmndrs/zustand
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v4.4.2
Choose a base ref
...
head repository: pmndrs/zustand
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v4.4.3
Choose a head ref
  • 4 commits
  • 11 files changed
  • 4 contributors

Commits on Oct 4, 2023

  1. Update testing docs (#2098)

    * Update testing docs
    
    * Minor changes
    
    * Minor changes
    
    * Minor changes
    
    * Minor changes
    
    * Minor changes
    
    * Update docs/guides/testing.md
    
    Co-authored-by: Blazej Sewera <code@sewera.dev>
    
    ---------
    
    Co-authored-by: Blazej Sewera <code@sewera.dev>
    dbritto-dev and sewera authored Oct 4, 2023
    1
    Copy the full SHA
    2be79c9 View commit details

Commits on Oct 5, 2023

  1. fix(shallow): Extract shallow vanilla and react (#2097)

    * Update readmes
    
    * Splitting shallow in two modules
    
    * Update tests
    
    * Minor changes
    
    * Minor changes
    
    * Rename shadow.tests.tsx to shallow.test.tsx
    
    * Add new entrypoint for shallow/react
    
    * Update structure
    
    * Update shallow to export from vanilla and react
    
    * Add vanilla/shallow and react/shallow entrypoints
    
    * Update tests
    
    * Update readmes
    
    * Update src/shallow.ts
    
    Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com>
    
    * Minor changes
    
    * Update readmes
    
    * Update readmes
    
    * Update tests
    
    * Minor changes
    
    ---------
    
    Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com>
    dbritto-dev and dai-shi authored Oct 5, 2023
    1
    Copy the full SHA
    e414f7c View commit details
  2. 1
    Copy the full SHA
    73ffa4b View commit details
  3. 4.4.3

    dai-shi committed Oct 5, 2023
    1
    Copy the full SHA
    90915ad View commit details
Showing with 346 additions and 169 deletions.
  1. +1 −1 docs/guides/prevent-rerenders-with-use-shallow.md
  2. +12 −1 docs/guides/testing.md
  3. +21 −1 package.json
  4. +1 −1 readme.md
  5. +1 −1 src/middleware/devtools.ts
  6. +13 −0 src/react/shallow.ts
  7. +5 −60 src/shallow.ts
  8. +49 −0 src/vanilla/shallow.ts
  9. +2 −104 tests/shallow.test.tsx
  10. +136 −0 tests/vanilla/basic.test.ts
  11. +105 −0 tests/vanilla/shallow.test.tsx
2 changes: 1 addition & 1 deletion docs/guides/prevent-rerenders-with-use-shallow.md
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ We can fix that using `useShallow`!

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

const useMeals = create(() => ({
papaBear: 'large porridge-pot',
13 changes: 12 additions & 1 deletion docs/guides/testing.md
Original file line number Diff line number Diff line change
@@ -57,6 +57,11 @@ this means your application logic does not need to be changed or mocked when wri
The mock provided below will enable the relevant test runner to reset the zustand stores after each test.

> **Warning:** In the following examples we are only supporting the curried version of create `(create()(...))`,
> since it is supported on both JavaScript and TypeScript. If you are using the uncurried version of
> `create(...)` you will need to update your hooks to use the curried version, or update the mocks in order
> to support the uncurried version.
### Jest

In the next steps we are going to setup our Jest environment in order to mock Zustand.
@@ -130,7 +135,13 @@ export default config
### Vitest

In the next steps we are going to setup our Vitest environment in order to mock Zustand
In the next steps we are going to setup our Vitest environment in order to mock Zustand.

> **Warning:** In Vitest you can change the [root](https://vitest.dev/config/#root).
> Due to that, you need make sure that you are creating your `__mocks__` directory in the right place.
> Let's say that you change the **root** to `./src`, that means you need to create a `__mocks__`
> directory under `./src`. The end result would be `./src/__mocks__`, rather than `./__mocks__`.
> Creating `__mocks__` directory in the wrong place can lead to issues when using Vitest.
```ts
// __mocks__/zustand.ts
22 changes: 21 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zustand",
"private": true,
"version": "4.4.2",
"version": "4.4.3",
"description": "🐻 Bear necessities for state management in React",
"main": "./index.js",
"types": "./index.d.ts",
@@ -65,6 +65,24 @@
"module": "./esm/shallow.js",
"default": "./shallow.js"
},
"./vanilla/shallow": {
"types": "./vanilla/shallow.d.ts",
"import": {
"types": "./esm/vanilla/shallow.d.mts",
"default": "./esm/vanilla/shallow.mjs"
},
"module": "./esm/vanilla/shallow.js",
"default": "./vanilla/shallow.js"
},
"./react/shallow": {
"types": "./react/shallow.d.ts",
"import": {
"types": "./esm/react/shallow.d.mts",
"default": "./esm/react/shallow.mjs"
},
"module": "./esm/react/shallow.js",
"default": "./react/shallow.js"
},
"./traditional": {
"types": "./traditional.d.ts",
"import": {
@@ -93,6 +111,8 @@
"build:middleware": "rollup -c --config-middleware",
"build:middleware:immer": "rollup -c --config-middleware_immer",
"build:shallow": "rollup -c --config-shallow",
"build:vanilla:shallow": "rollup -c --config-vanilla_shallow",
"build:react:shallow": "rollup -c --config-react_shallow",
"build:traditional": "rollup -c --config-traditional",
"build:context": "rollup -c --config-context",
"postbuild": "yarn patch-d-ts && yarn copy && yarn patch-esm-ts",
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
@@ -88,7 +88,7 @@ If you want to construct a single object with multiple state-picks inside, simil

```jsx
import { create } from 'zustand'
import { useShallow } from 'zustand/shallow'
import { useShallow } from 'zustand/react/shallow'

const useBearStore = create((set) => ({
bears: 0,
2 changes: 1 addition & 1 deletion src/middleware/devtools.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import type {
type Config = Parameters<
(Window extends { __REDUX_DEVTOOLS_EXTENSION__?: infer T }
? T
: any)['connect']
: { connect: (param: any) => any })['connect']
>[0]

declare module '../vanilla' {
13 changes: 13 additions & 0 deletions src/react/shallow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useRef } from 'react'
import { shallow } from '../vanilla/shallow.ts'

export function useShallow<S, U>(selector: (state: S) => U): (state: S) => U {
const prev = useRef<U>()

return (state) => {
const next = selector(state)
return shallow(prev.current, next)
? (prev.current as U)
: (prev.current = next)
}
}
65 changes: 5 additions & 60 deletions src/shallow.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,8 @@
import { useRef } from 'react'
import { shallow } from './vanilla/shallow.ts'

export function shallow<T>(objA: T, objB: T) {
if (Object.is(objA, objB)) {
return true
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false
}

if (objA instanceof Map && objB instanceof Map) {
if (objA.size !== objB.size) return false

for (const [key, value] of objA) {
if (!Object.is(value, objB.get(key))) {
return false
}
}
return true
}

if (objA instanceof Set && objB instanceof Set) {
if (objA.size !== objB.size) return false

for (const value of objA) {
if (!objB.has(value)) {
return false
}
}
return true
}

const keysA = Object.keys(objA)
if (keysA.length !== Object.keys(objB).length) {
return false
}
for (let i = 0; i < keysA.length; i++) {
if (
!Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) ||
!Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T])
) {
return false
}
}
return true
}
// We will export this in v5 and remove default export
// export { shallow } from './vanilla/shallow.ts'
// export { useShallow } from './react/shallow.ts'

/**
* @deprecated Use `import { shallow } from 'zustand/shallow'`
@@ -62,13 +16,4 @@ export default ((objA, objB) => {
return shallow(objA, objB)
}) as typeof shallow

export function useShallow<S, U>(selector: (state: S) => U): (state: S) => U {
const prev = useRef<U>()

return (state) => {
const next = selector(state)
return shallow(prev.current, next)
? (prev.current as U)
: (prev.current = next)
}
}
export { shallow }
49 changes: 49 additions & 0 deletions src/vanilla/shallow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export function shallow<T>(objA: T, objB: T) {
if (Object.is(objA, objB)) {
return true
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false
}

if (objA instanceof Map && objB instanceof Map) {
if (objA.size !== objB.size) return false

for (const [key, value] of objA) {
if (!Object.is(value, objB.get(key))) {
return false
}
}
return true
}

if (objA instanceof Set && objB instanceof Set) {
if (objA.size !== objB.size) return false

for (const value of objA) {
if (!objB.has(value)) {
return false
}
}
return true
}

const keysA = Object.keys(objA)
if (keysA.length !== Object.keys(objB).length) {
return false
}
for (let i = 0; i < keysA.length; i++) {
if (
!Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) ||
!Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T])
) {
return false
}
}
return true
}
106 changes: 2 additions & 104 deletions tests/shallow.test.tsx
Original file line number Diff line number Diff line change
@@ -2,99 +2,8 @@ import { useState } from 'react'
import { act, fireEvent, render } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { create } from 'zustand'
import { shallow, useShallow } from 'zustand/shallow'

describe('shallow', () => {
it('compares primitive values', () => {
expect(shallow(true, true)).toBe(true)
expect(shallow(true, false)).toBe(false)

expect(shallow(1, 1)).toBe(true)
expect(shallow(1, 2)).toBe(false)

expect(shallow('zustand', 'zustand')).toBe(true)
expect(shallow('zustand', 'redux')).toBe(false)
})

it('compares objects', () => {
expect(shallow({ foo: 'bar', asd: 123 }, { foo: 'bar', asd: 123 })).toBe(
true
)

expect(
shallow({ foo: 'bar', asd: 123 }, { foo: 'bar', foobar: true })
).toBe(false)

expect(
shallow({ foo: 'bar', asd: 123 }, { foo: 'bar', asd: 123, foobar: true })
).toBe(false)
})

it('compares arrays', () => {
expect(shallow([1, 2, 3], [1, 2, 3])).toBe(true)

expect(shallow([1, 2, 3], [2, 3, 4])).toBe(false)

expect(
shallow([{ foo: 'bar' }, { asd: 123 }], [{ foo: 'bar' }, { asd: 123 }])
).toBe(false)

expect(shallow([{ foo: 'bar' }], [{ foo: 'bar', asd: 123 }])).toBe(false)
})

it('compares Maps', () => {
function createMap<T extends object>(obj: T) {
return new Map(Object.entries(obj))
}

expect(
shallow(
createMap({ foo: 'bar', asd: 123 }),
createMap({ foo: 'bar', asd: 123 })
)
).toBe(true)

expect(
shallow(
createMap({ foo: 'bar', asd: 123 }),
createMap({ foo: 'bar', foobar: true })
)
).toBe(false)

expect(
shallow(
createMap({ foo: 'bar', asd: 123 }),
createMap({ foo: 'bar', asd: 123, foobar: true })
)
).toBe(false)
})

it('compares Sets', () => {
expect(shallow(new Set(['bar', 123]), new Set(['bar', 123]))).toBe(true)

expect(shallow(new Set(['bar', 123]), new Set(['bar', 2]))).toBe(false)

expect(shallow(new Set(['bar', 123]), new Set(['bar', 123, true]))).toBe(
false
)
})

it('compares functions', () => {
function firstFnCompare() {
return { foo: 'bar' }
}

function secondFnCompare() {
return { foo: 'bar' }
}

expect(shallow(firstFnCompare, firstFnCompare)).toBe(true)

expect(shallow(secondFnCompare, secondFnCompare)).toBe(true)

expect(shallow(firstFnCompare, secondFnCompare)).toBe(false)
})
})
import { useShallow } from 'zustand/react/shallow'
import { shallow } from 'zustand/vanilla/shallow'

describe('types', () => {
it('works with useBoundStore and array selector (#1107)', () => {
@@ -123,17 +32,6 @@ describe('types', () => {
})
})

describe('unsupported cases', () => {
it('date', () => {
expect(
shallow(
new Date('2022-07-19T00:00:00.000Z'),
new Date('2022-07-20T00:00:00.000Z')
)
).not.toBe(false)
})
})

describe('useShallow', () => {
const testUseShallowSimpleCallback =
vi.fn<[{ selectorOutput: string[]; useShallowOutput: string[] }]>()
Loading