Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
cf511cf
imaginary code that uses uSES
dai-shi Aug 31, 2021
5570b10
revert backward compatibility code as this is not going to be v4
dai-shi Aug 31, 2021
b03b226
use use-sync-external-store
dai-shi Sep 4, 2021
8112285
revert to react 17
dai-shi Sep 4, 2021
50f2963
handle error by our own
dai-shi Sep 4, 2021
94d9d4a
v4.0.0-alpha.2
dai-shi Sep 4, 2021
d36611b
fix&refactor a bit
dai-shi Sep 6, 2021
453e59a
Merge branch 'main' into use-sync-external-store
dai-shi Sep 14, 2021
ef89673
update uSES experimental package
dai-shi Sep 14, 2021
d61a0b7
remove error propagation hack
dai-shi Sep 14, 2021
ec117e6
update size snapshot
dai-shi Sep 14, 2021
e2081c8
update uSES and add dts
dai-shi Sep 27, 2021
1fee70e
split react.ts and no export wild
dai-shi Sep 27, 2021
9aaeb5e
split useStore impl
dai-shi Sep 27, 2021
a914052
context to follow the new api, export wild again
dai-shi Sep 27, 2021
9021456
v4.0.0-alpha.3
dai-shi Sep 27, 2021
b9a70f2
add missing await
dai-shi Sep 28, 2021
98c59d3
merge main
dai-shi Sep 28, 2021
c9c8ae9
merge main
dai-shi Oct 1, 2021
cc391bc
update uSES
dai-shi Oct 1, 2021
bef6f81
update uSES
dai-shi Oct 3, 2021
d78e91f
Merge branch 'main' into use-sync-external-store
dai-shi Oct 3, 2021
54a5b8c
uses uSES extra!
dai-shi Oct 4, 2021
66fc5a7
Merge branch 'main' into use-sync-external-store
dai-shi Oct 4, 2021
1ee93aa
v4.0.0-alpha.3
dai-shi Oct 4, 2021
c643ebf
merge main
dai-shi Oct 7, 2021
a20e4d1
merge main
dai-shi Oct 19, 2021
56d512a
merge main
dai-shi Oct 21, 2021
3b03362
merge main
dai-shi Oct 27, 2021
24d9750
update uSES
dai-shi Oct 27, 2021
f75e868
fix update uSES
dai-shi Oct 27, 2021
2ed60eb
v4.0.0-alpha.5
dai-shi Oct 27, 2021
9675378
merge main
dai-shi Oct 30, 2021
8fad667
add useDebugValue
dai-shi Oct 31, 2021
7575357
update uSES
dai-shi Oct 31, 2021
e51f75a
update uSES types
dai-shi Nov 2, 2021
7941091
merge main
dai-shi Nov 2, 2021
e7adbbf
update uSES
dai-shi Nov 2, 2021
5812d0e
v4.0.0-alpha.6
dai-shi Nov 2, 2021
9690a11
merge main
dai-shi Nov 4, 2021
f037a2a
fix(readme): remove memoization section which is no longer valid with…
dai-shi Nov 4, 2021
8339d52
feat(readme): add new createStore/useStore usage
dai-shi Nov 4, 2021
2e5ab21
merge main
dai-shi Nov 9, 2021
37119f5
update useSES
dai-shi Nov 9, 2021
835684a
update uSES and deps
dai-shi Nov 15, 2021
20abedf
Merge branch 'main' into use-sync-external-store
dai-shi Nov 15, 2021
e91bde6
v4.0.0-alpha.7
dai-shi Nov 15, 2021
c009886
merge main
dai-shi Nov 16, 2021
be18127
merge main
dai-shi Nov 22, 2021
9280262
update uSES
dai-shi Nov 22, 2021
c6acc59
merge main
dai-shi Dec 3, 2021
303fd82
update uSES
dai-shi Dec 7, 2021
0d4e0d8
shave bytes
dai-shi Dec 7, 2021
445bc8f
Merge branch 'main' into use-sync-external-store
dai-shi Dec 7, 2021
dd57cb0
Merge branch 'main' into use-sync-external-store
dai-shi Dec 24, 2021
76b1d4c
update uSES
dai-shi Dec 24, 2021
90fd7ee
Merge branch 'main' into use-sync-external-store
dai-shi Feb 11, 2022
ff7358a
Merge branch 'main' into use-sync-external-store
dai-shi Feb 28, 2022
92a7094
fix yarn lock
dai-shi Feb 28, 2022
64536ee
temporary fix #829
dai-shi Feb 28, 2022
421454a
Merge branch 'main' into use-sync-external-store
dai-shi Mar 4, 2022
33ac559
uSES rc.1
dai-shi Mar 4, 2022
773e2e2
getServerState for #886, no types yet
dai-shi Mar 31, 2022
90ef5c1
uSES v1
dai-shi Apr 5, 2022
748caeb
Merge branch 'main' into use-sync-external-store
dai-shi Apr 7, 2022
1be05d1
Merge branch 'main' into use-sync-external-store
dai-shi Apr 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
{
"index.js": {
"bundled": 3993,
"minified": 1614,
"gzipped": 786,
"bundled": 2419,
"minified": 919,
"gzipped": 471,
"treeshaked": {
"rollup": {
"code": 124,
"import_statements": 14
"code": 46,
"import_statements": 46
},
"webpack": {
"code": 1144
"code": 1063
}
}
},
"index.mjs": {
"bundled": 3993,
"minified": 1614,
"gzipped": 786,
"bundled": 2419,
"minified": 919,
"gzipped": 471,
"treeshaked": {
"rollup": {
"code": 124,
"import_statements": 14
"code": 46,
"import_statements": 46
},
"webpack": {
"code": 1144
"code": 1063
}
}
},
"vanilla.js": {
"bundled": 1352,
"minified": 444,
"gzipped": 288,
"bundled": 1362,
"minified": 454,
"gzipped": 293,
"treeshaked": {
"rollup": {
"code": 0,
Expand All @@ -42,9 +42,9 @@
}
},
"vanilla.mjs": {
"bundled": 1352,
"minified": 444,
"gzipped": 288,
"bundled": 1362,
"minified": 454,
"gzipped": 293,
"treeshaked": {
"rollup": {
"code": 0,
Expand Down Expand Up @@ -112,30 +112,30 @@
}
},
"context.js": {
"bundled": 1516,
"minified": 840,
"gzipped": 418,
"bundled": 1236,
"minified": 753,
"gzipped": 358,
"treeshaked": {
"rollup": {
"code": 14,
"import_statements": 14
"code": 30,
"import_statements": 30
},
"webpack": {
"code": 998
"code": 1047
}
}
},
"context.mjs": {
"bundled": 1516,
"minified": 840,
"gzipped": 418,
"bundled": 1236,
"minified": 753,
"gzipped": 358,
"treeshaked": {
"rollup": {
"code": 14,
"import_statements": 14
"code": 30,
"import_statements": 30
},
"webpack": {
"code": 998
"code": 1047
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"name": "zustand",
"private": true,
"version": "3.5.10",
"version": "4.0.0-alpha.3",
"publishConfig": {
"tag": "alpha"
},
"description": "🐻 Bear necessities for state management in React",
"main": "./index.js",
"types": "./index.d.ts",
Expand Down Expand Up @@ -118,6 +121,9 @@
"tests/**/*.{js,ts,tsx}"
]
},
"dependencies": {
"use-sync-external-store": "^0.0.0-experimental-b1a1cb116-20210924"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-external-helpers": "^7.14.5",
Expand All @@ -132,6 +138,7 @@
"@types/jest": "^27.0.1",
"@types/react": "^17.0.19",
"@types/react-dom": "^17.0.9",
"@types/use-sync-external-store": "^0.0.0",
"@typescript-eslint/eslint-plugin": "^4.29.3",
"@typescript-eslint/parser": "^4.29.3",
"concurrently": "^6.2.1",
Expand Down
55 changes: 23 additions & 32 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,34 @@ import {
useMemo,
useRef,
} from 'react'
import { EqualityChecker, UseStore } from 'zustand'
import { State, StateSelector } from './vanilla'
import {
EqualityChecker,
State,
StateSelector,
StoreApi,
useStore,
} from 'zustand'

export interface UseContextStore<T extends State> {
(): T
<U>(selector: StateSelector<T, U>, equalityFn?: EqualityChecker<U>): U
}

function createContext<TState extends State>() {
const ZustandContext = reactCreateContext<UseStore<TState> | undefined>(
const ZustandContext = reactCreateContext<StoreApi<TState> | undefined>(
undefined
)

const Provider = ({
initialStore,
createStore,
children,
}: {
/**
* @deprecated
*/
initialStore?: UseStore<TState>
createStore: () => UseStore<TState>
createStore: () => StoreApi<TState>
children: ReactNode
}) => {
const storeRef = useRef<UseStore<TState>>()
const storeRef = useRef<StoreApi<TState>>()

if (!storeRef.current) {
if (initialStore) {
console.warn(
'Provider initialStore is deprecated and will be removed in the next version.'
)
if (!createStore) {
createStore = () => initialStore
}
}
storeRef.current = createStore()
}

Expand All @@ -52,45 +44,44 @@ function createContext<TState extends State>() {
)
}

const useStore: UseContextStore<TState> = <StateSlice>(
const useBoundStore: UseContextStore<TState> = <StateSlice>(
selector?: StateSelector<TState, StateSlice>,
equalityFn = Object.is
) => {
// ZustandContext value is guaranteed to be stable.
const useProviderStore = useContext(ZustandContext)
if (!useProviderStore) {
const store = useContext(ZustandContext)
if (!store) {
throw new Error(
'Seems like you have not used zustand provider as an ancestor.'
)
}
return useProviderStore(
return useStore(
store,
selector as StateSelector<TState, StateSlice>,
equalityFn
)
}

const useStoreApi = () => {
// ZustandContext value is guaranteed to be stable.
const useProviderStore = useContext(ZustandContext)
if (!useProviderStore) {
const store = useContext(ZustandContext)
if (!store) {
throw new Error(
'Seems like you have not used zustand provider as an ancestor.'
)
}
return useMemo(
() => ({
getState: useProviderStore.getState,
setState: useProviderStore.setState,
subscribe: useProviderStore.subscribe,
destroy: useProviderStore.destroy,
getState: store.getState,
setState: store.setState,
subscribe: store.subscribe,
destroy: store.destroy,
}),
[useProviderStore]
[store]
)
}

return {
Provider,
useStore,
useStore: useBoundStore,
useStoreApi,
}
}
Expand Down
147 changes: 11 additions & 136 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,137 +1,12 @@
import { useEffect, useLayoutEffect, useReducer, useRef } from 'react'
import createImpl, {
Destroy,
EqualityChecker,
GetState,
SetState,
State,
StateCreator,
StateSelector,
StoreApi,
Subscribe,
} from './vanilla'
export * from './vanilla'

// For server-side rendering: https://github.com/pmndrs/zustand/pull/34
// Deno support: https://github.com/pmndrs/zustand/issues/347
const isSSR =
typeof window === 'undefined' ||
!window.navigator ||
/ServerSideRendering|^Deno\//.test(window.navigator.userAgent)

const useIsomorphicLayoutEffect = isSSR ? useEffect : useLayoutEffect

export interface UseStore<T extends State> {
(): T
<U>(selector: StateSelector<T, U>, equalityFn?: EqualityChecker<U>): U
setState: SetState<T>
getState: GetState<T>
subscribe: Subscribe<T>
destroy: Destroy
}

export default function create<TState extends State>(
createState: StateCreator<TState> | StoreApi<TState>
): UseStore<TState> {
const api: StoreApi<TState> =
typeof createState === 'function' ? createImpl(createState) : createState

const useStore: any = <StateSlice>(
selector: StateSelector<TState, StateSlice> = api.getState as any,
equalityFn: EqualityChecker<StateSlice> = Object.is
) => {
const [, forceUpdate] = useReducer((c) => c + 1, 0) as [never, () => void]

const state = api.getState()
const stateRef = useRef(state)
const selectorRef = useRef(selector)
const equalityFnRef = useRef(equalityFn)
const erroredRef = useRef(false)

const currentSliceRef = useRef<StateSlice>()
if (currentSliceRef.current === undefined) {
currentSliceRef.current = selector(state)
}

let newStateSlice: StateSlice | undefined
let hasNewStateSlice = false

// The selector or equalityFn need to be called during the render phase if
// they change. We also want legitimate errors to be visible so we re-run
// them if they errored in the subscriber.
if (
stateRef.current !== state ||
selectorRef.current !== selector ||
equalityFnRef.current !== equalityFn ||
erroredRef.current
) {
// Using local variables to avoid mutations in the render phase.
newStateSlice = selector(state)
hasNewStateSlice = !equalityFn(
currentSliceRef.current as StateSlice,
newStateSlice
)
}

// Syncing changes in useEffect.
useIsomorphicLayoutEffect(() => {
if (hasNewStateSlice) {
currentSliceRef.current = newStateSlice as StateSlice
}
stateRef.current = state
selectorRef.current = selector
equalityFnRef.current = equalityFn
erroredRef.current = false
})

const stateBeforeSubscriptionRef = useRef(state)
useIsomorphicLayoutEffect(() => {
const listener = () => {
try {
const nextState = api.getState()
const nextStateSlice = selectorRef.current(nextState)
if (
!equalityFnRef.current(
currentSliceRef.current as StateSlice,
nextStateSlice
)
) {
stateRef.current = nextState
currentSliceRef.current = nextStateSlice
forceUpdate()
}
} catch (error) {
erroredRef.current = true
forceUpdate()
}
}
const unsubscribe = api.subscribe(listener)
if (api.getState() !== stateBeforeSubscriptionRef.current) {
listener() // state has changed before subscription
}
return unsubscribe
}, [])

return hasNewStateSlice
? (newStateSlice as StateSlice)
: currentSliceRef.current
}

Object.assign(useStore, api)

// For backward compatibility (No TS types for this)
useStore[Symbol.iterator] = function () {
console.warn(
'[useStore, api] = create() is deprecated and will be removed in v4'
)
const items = [useStore, api]
return {
next() {
const done = items.length <= 0
return { value: items.shift(), done }
},
}
}

return useStore
}
export { default as createStore } from './vanilla'
export * from './react'
export { default } from './react'

// For v3 compatibility
import { UseBoundStore } from './react'
import { State } from './vanilla'
/**
* @deprecated rename to UseBoundStore
*/
export interface UseStore<T extends State> extends UseBoundStore<T> {}
Loading