breaking(middleware/devtools): remove deprecations and warnings (#892)

* imaginary code that uses uSES

* revert backward compatibility code as this is not going to be v4

* use use-sync-external-store

* revert to react 17

* handle error by our own

* v4.0.0-alpha.2

* fix&refactor a bit

* update uSES experimental package

* remove error propagation hack

* update size snapshot

* update uSES and add dts

* split react.ts and no export wild

* split useStore impl

* context to follow the new api, export wild again

* v4.0.0-alpha.3

* add missing await

* update uSES

* update uSES

* uses uSES extra!

* v4.0.0-alpha.3

* fix(types): Rename from UseStore to UseBoundStore

* breaking(types): drop deprecated UseStore type

* breaking(core): drop v2 hook compatibility

* breaking(middleware): drop deprecated persist options

* breaking(core): drop deprecated store.subscribe with selector

* update uSES

* fix update uSES

* v4.0.0-alpha.5

* combine subscribe type

* intentional undefined type

* add useDebugValue

* update uSES

* update uSES types

* breaking(middleware): make persist options.removeItem required

* update uSES

* v4.0.0-alpha.6

* fix(readme): remove memoization section which is no longer valid with uSES

* feat(readme): add new createStore/useStore usage

* update useSES

* update uSES and deps

* v4.0.0-alpha.7

* update uSES

* update uSES

* shave bytes

* update uSES

* breaking(middleware/devtools): use official devtools extension types

* type object.create

* avoid emitting @redux-devtools/extension

* fix type with any

* refactor

* fix yarn lock

* temporary fix #829

* v4.0.0-beta.2

* fix lint

* lock date-fns version

* lock testing-library/react alpha version

* devtools: remove deprecations and warnings

* fix tests

* use `__DEV__`

* revert irrelevant v4 changes

* correct merge

* fix test

* fix `any`

* fix `any` again

Co-authored-by: daishi <daishi@axlight.com>
This commit is contained in:
Devansh Jethmalani 2022-04-08 22:57:28 +05:30 committed by GitHub
parent f65a25acd5
commit 943af082ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 69 additions and 173 deletions

View File

@ -18,15 +18,7 @@ type Message = {
type Write<T extends object, U extends object> = Omit<T, keyof U> & U
type Cast<T, U> = T extends U ? T : U
type WithDevtools<S> = Write<Cast<S, object>, StoreSetStateWithAction<S>> & {
/**
* @deprecated `devtools` property on the store is deprecated
* it will be removed in the next major.
* You shouldn't interact with the extension directly. But in case you still want to
* you can patch `window.__REDUX_DEVTOOLS_EXTENSION__` directly
*/
devtools?: DevtoolsType
}
type WithDevtools<S> = Write<Cast<S, object>, StoreSetStateWithAction<S>>
type StoreSetStateWithAction<S> = S extends { getState: () => infer T }
? S & { setState: NamedSet<Cast<T, object>> }
@ -50,23 +42,6 @@ interface DevtoolsOptions {
}
}
type DevtoolsType = {
/**
* @deprecated along with `api.devtools`, `api.devtools.prefix` is deprecated.
* We no longer prefix the actions/names, because the `name` option already
* creates a separate instance of devtools for each store.
*/
prefix: string
subscribe: (dispatch: any) => () => void
unsubscribe: () => void
send: {
(action: string | { type: unknown }, state: any): void
(action: null, liftedState: any): void
}
init: (state: any) => void
error: (payload: any) => void
}
export type NamedSet<T extends State> = {
<
K1 extends keyof T,
@ -85,13 +60,6 @@ export type NamedSet<T extends State> = {
*/
export type StoreApiWithDevtools<T extends State> = StoreApi<T> & {
setState: NamedSet<T>
/**
* @deprecated `devtools` property on the store is deprecated
* it will be removed in the next major.
* You shouldn't interact with the extension directly. But in case you still want to
* you can patch `window.__REDUX_DEVTOOLS_EXTENSION__` directly
*/
devtools?: DevtoolsType
}
export function devtools<
@ -104,11 +72,7 @@ export function devtools<
): (
set: CustomSetState,
get: CustomGetState,
api: CustomStoreApi &
StoreApiWithDevtools<S> & {
dispatch?: unknown
dispatchFromDevtools?: boolean
}
api: CustomStoreApi & StoreApiWithDevtools<S>
) => S
/**
* @deprecated Passing `name` as directly will be not allowed in next major.
@ -125,11 +89,7 @@ export function devtools<
): (
set: CustomSetState,
get: CustomGetState,
api: CustomStoreApi &
StoreApiWithDevtools<S> & {
dispatch?: unknown
dispatchFromDevtools?: boolean
}
api: CustomStoreApi & StoreApiWithDevtools<S>
) => S
export function devtools<
S extends State,
@ -142,11 +102,7 @@ export function devtools<
): (
set: CustomSetState,
get: CustomGetState,
api: CustomStoreApi &
StoreApiWithDevtools<S> & {
dispatch?: unknown
dispatchFromDevtools?: boolean
}
api: CustomStoreApi & StoreApiWithDevtools<S>
) => S
export function devtools<
S extends State,
@ -160,20 +116,8 @@ export function devtools<
return (
set: CustomSetState,
get: CustomGetState,
api: CustomStoreApi &
StoreApiWithDevtools<S> & {
dispatch?: unknown
dispatchFromDevtools?: boolean
}
api: CustomStoreApi & StoreApiWithDevtools<S>
): S => {
let didWarnAboutNameDeprecation = false
if (typeof options === 'string' && !didWarnAboutNameDeprecation) {
console.warn(
'[zustand devtools middleware]: passing `name` as directly will be not allowed in next major' +
'pass the `name` in an object `{ name: ... }` instead'
)
didWarnAboutNameDeprecation = true
}
const devtoolsOptions =
options === undefined
? {}
@ -197,71 +141,7 @@ export function devtools<
return fn(set, get, api)
}
let extension = (Object.create as <T>(t: T) => T)(
extensionConnector.connect(devtoolsOptions)
)
// We're using `Object.defineProperty` to set `prefix`, so if extensionConnector.connect
// returns the same reference we'd get cannot redefine property prefix error
// hence we `Object.create` to make a new reference
let didWarnAboutDevtools = false
Object.defineProperty(api, 'devtools', {
get: () => {
if (!didWarnAboutDevtools) {
console.warn(
'[zustand devtools middleware] `devtools` property on the store is deprecated ' +
'it will be removed in the next major.\n' +
"You shouldn't interact with the extension directly. But in case you still want to " +
'you can patch `window.__REDUX_DEVTOOLS_EXTENSION__` directly'
)
didWarnAboutDevtools = true
}
return extension
},
set: (value) => {
if (!didWarnAboutDevtools) {
console.warn(
'[zustand devtools middleware] `api.devtools` is deprecated, ' +
'it will be removed in the next major.\n' +
"You shouldn't interact with the extension directly. But in case you still want to " +
'you can patch `window.__REDUX_DEVTOOLS_EXTENSION__` directly'
)
didWarnAboutDevtools = true
}
extension = value
},
})
let didWarnAboutPrefix = false
Object.defineProperty(extension, 'prefix', {
get: () => {
if (!didWarnAboutPrefix) {
console.warn(
'[zustand devtools middleware] along with `api.devtools`, `api.devtools.prefix` is deprecated.\n' +
'We no longer prefix the actions/names' +
devtoolsOptions.name ===
undefined
? ', pass the `name` option to create a separate instance of devtools for each store.'
: ', because the `name` option already creates a separate instance of devtools for each store.'
)
didWarnAboutPrefix = true
}
return ''
},
set: () => {
if (!didWarnAboutPrefix) {
console.warn(
'[zustand devtools middleware] along with `api.devtools`, `api.devtools.prefix` is deprecated.\n' +
'We no longer prefix the actions/names' +
devtoolsOptions.name ===
undefined
? ', pass the `name` option to create a separate instance of devtools for each store.'
: ', because the `name` option already creates a separate instance of devtools for each store.'
)
didWarnAboutPrefix = true
}
},
})
const extension = extensionConnector.connect(devtoolsOptions)
let isRecording = true
;(api.setState as NamedSet<S>) = (state, replace, nameOrAction) => {
@ -286,11 +166,18 @@ export function devtools<
const initialState = fn(api.setState, get, api)
extension.init(initialState)
if (api.dispatchFromDevtools && typeof api.dispatch === 'function') {
if (
(api as any).dispatchFromDevtools &&
typeof (api as any).dispatch === 'function'
) {
let didWarnAboutReservedActionType = false
const originalDispatch = api.dispatch
api.dispatch = (...a: any[]) => {
if (a[0].type === '__setState' && !didWarnAboutReservedActionType) {
const originalDispatch = (api as any).dispatch
;(api as any).dispatch = (...a: any[]) => {
if (
__DEV__ &&
a[0].type === '__setState' &&
!didWarnAboutReservedActionType
) {
console.warn(
'[zustand devtools middleware] "__setState" action type is reserved ' +
'to set state from the devtools. Avoid using it.'
@ -325,9 +212,9 @@ export function devtools<
return
}
if (!api.dispatchFromDevtools) return
if (typeof api.dispatch !== 'function') return
;(api.dispatch as any)(action)
if (!(api as any).dispatchFromDevtools) return
if (typeof (api as any).dispatch !== 'function') return
;(api as any).dispatch(action)
}
)

View File

@ -1,5 +1,5 @@
import { devtools, redux } from 'zustand/middleware'
import create from 'zustand/vanilla'
import create, { StoreApi } from 'zustand/vanilla'
let extensionSubscriber: ((message: any) => void) | undefined
const extension = {
@ -125,7 +125,7 @@ describe('when it receives an message of type...', () => {
it('does nothing even if there is `api.dispatch`', () => {
const initialState = { count: 0 }
const api = create(devtools(() => initialState))
api.dispatch = jest.fn()
;(api as any).dispatch = jest.fn()
const setState = jest.spyOn(api, 'setState')
;(extensionSubscriber as (message: any) => void)({
@ -135,14 +135,14 @@ describe('when it receives an message of type...', () => {
expect(api.getState()).toBe(initialState)
expect(setState).not.toBeCalled()
expect(api.dispatch).not.toBeCalled()
expect((api as any).dispatch).not.toBeCalled()
})
it('dispatches with `api.dispatch` when `api.dispatchFromDevtools` is set to true', () => {
const initialState = { count: 0 }
const api = create(devtools(() => initialState))
api.dispatch = jest.fn()
api.dispatchFromDevtools = true
;(api as any).dispatch = jest.fn()
;(api as any).dispatchFromDevtools = true
const setState = jest.spyOn(api, 'setState')
;(extensionSubscriber as (message: any) => void)({
@ -152,14 +152,16 @@ describe('when it receives an message of type...', () => {
expect(api.getState()).toBe(initialState)
expect(setState).not.toBeCalled()
expect(api.dispatch).toHaveBeenLastCalledWith({ type: 'INCREMENT' })
expect((api as any).dispatch).toHaveBeenLastCalledWith({
type: 'INCREMENT',
})
})
it('does not throw for unsupported payload', () => {
const initialState = { count: 0 }
const api = create(devtools(() => initialState))
api.dispatch = jest.fn()
api.dispatchFromDevtools = true
;(api as any).dispatch = jest.fn()
;(api as any).dispatchFromDevtools = true
const setState = jest.spyOn(api, 'setState')
const originalConsoleError = console.error
console.error = jest.fn()
@ -195,7 +197,7 @@ describe('when it receives an message of type...', () => {
expect(api.getState()).toBe(initialState)
expect(setState).not.toBeCalled()
expect(api.dispatch).not.toBeCalled()
expect((api as any).dispatch).not.toBeCalled()
console.error = originalConsoleError
})
@ -436,43 +438,50 @@ describe('when it receives an message of type...', () => {
})
})
it('works with redux middleware', () => {
const api = create(
devtools(
redux(
({ count }, { type }: { type: 'INCREMENT' | 'DECREMENT' }) => ({
count: count + (type === 'INCREMENT' ? 1 : -1),
}),
{ count: 0 }
describe('with redux middleware', () => {
let api: StoreApi<{ count: number }>
it('works as expected', () => {
api = create(
devtools(
redux(
(
{ count },
{ type }: { type: 'INCREMENT' } | { type: 'DECREMENT' }
) => ({
count: count + (type === 'INCREMENT' ? 1 : -1),
}),
{ count: 0 }
)
)
)
)
;(api as any).dispatch({ type: 'INCREMENT' })
;(api as any).dispatch({ type: 'INCREMENT' })
;(extensionSubscriber as (message: any) => void)({
type: 'ACTION',
payload: JSON.stringify({ type: 'DECREMENT' }),
})
api.dispatch({ type: 'INCREMENT' })
api.dispatch({ type: 'INCREMENT' })
;(extensionSubscriber as (message: any) => void)({
type: 'ACTION',
payload: JSON.stringify({ type: 'DECREMENT' }),
expect(extension.init.mock.calls).toMatchObject([[{ count: 0 }]])
expect(extension.send.mock.calls).toMatchObject([
[{ type: 'INCREMENT' }, { count: 1 }],
[{ type: 'INCREMENT' }, { count: 2 }],
[{ type: 'DECREMENT' }, { count: 1 }],
])
expect(api.getState()).toMatchObject({ count: 1 })
})
expect(extension.init.mock.calls).toMatchObject([[{ count: 0 }]])
expect(extension.send.mock.calls).toMatchObject([
[{ type: 'INCREMENT' }, { count: 1 }],
[{ type: 'INCREMENT' }, { count: 2 }],
[{ type: 'DECREMENT' }, { count: 1 }],
])
expect(api.getState()).toMatchObject({ count: 1 })
it('[DEV-ONLY] warns about misusage', () => {
const originalConsoleWarn = console.warn
console.warn = jest.fn()
;(api as any).dispatch({ type: '__setState' as any })
expect(console.warn).toHaveBeenLastCalledWith(
'[zustand devtools middleware] "__setState" action type is reserved ' +
'to set state from the devtools. Avoid using it.'
)
const originalConsoleWarn = console.warn
console.warn = jest.fn()
api.dispatch({ type: '__setState' as any })
expect(console.warn).toHaveBeenLastCalledWith(
'[zustand devtools middleware] "__setState" action type is reserved ' +
'to set state from the devtools. Avoid using it.'
)
console.warn = originalConsoleWarn
console.warn = originalConsoleWarn
})
})
it('works in non-browser env', () => {