Repository: pmndrs/zustand
Pull request #933 (2022-04-25 14:23:09)

breaking(middleware/devtools): remove checking old string option
This is something we forgot in #892.
Also did some refactor to remove our own options in DevtoolsOptions.

dai-shi: review request @devanshj 
github-actions[bot]: **Size Change:** +725 B (+2%) 

**Total Size:** 29.8 kB

| Filename | Size | Change |  |
| :--- | :---: | :---: | :---: |
| `dist/esm/middleware.js` | 3.09 kB | +137 B (+5%) | 🔍 |
| `dist/middleware.js` | 3.22 kB | +149 B (+5%) | 🔍 |
| `dist/system/middleware.development.js` | 3.18 kB | +137 B (+5%) | 🔍 |
| `dist/system/middleware.production.js` | 2.04 kB | +71 B (+4%) |  |
| `dist/umd/middleware.development.js` | 3.33 kB | +150 B (+5%) | 🔍 |
| `dist/umd/middleware.production.js` | 2.05 kB | +81 B (+4%) |  |

<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size |
| :--- | :---: |
| `dist/context.js` | 518 B |
| `dist/esm/context.js` | 448 B |
| `dist/esm/index.js` | 400 B |
| `dist/esm/middleware/immer.js` | 210 B |
| `dist/esm/shallow.js` | 272 B |
| `dist/esm/vanilla.js` | 377 B |
| `dist/index.js` | 642 B |
| `dist/middleware/immer.js` | 373 B |
| `dist/shallow.js` | 318 B |
| `dist/system/context.development.js` | 572 B |
| `dist/system/context.production.js` | 393 B |
| `dist/system/index.development.js` | 575 B |
| `dist/system/index.production.js` | 385 B |
| `dist/system/middleware/immer.development.js` | 292 B |
| `dist/system/middleware/immer.production.js` | 187 B |
| `dist/system/shallow.development.js` | 338 B |
| `dist/system/shallow.production.js` | 253 B |
| `dist/system/vanilla.development.js` | 444 B |
| `dist/system/vanilla.production.js` | 270 B |
| `dist/umd/context.development.js` | 662 B |
| `dist/umd/context.production.js` | 488 B |
| `dist/umd/index.development.js` | 787 B |
| `dist/umd/index.production.js` | 564 B |
| `dist/umd/middleware/immer.development.js` | 514 B |
| `dist/umd/middleware/immer.production.js` | 371 B |
| `dist/umd/shallow.development.js` | 447 B |
| `dist/umd/shallow.production.js` | 344 B |
| `dist/umd/vanilla.development.js` | 590 B |
| `dist/umd/vanilla.production.js` | 378 B |
| `dist/vanilla.js` | 456 B |

</details>



<a href="https://github.com/preactjs/compressed-size-action"><sub>compressed-size-action</sub></a>
codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/pmndrs/zustand/pr/933/builds/242928) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit f679bbfef665fe11f2f94cec7aaed57fd765cf7b:

|Sandbox| Source |
|--|--|
|[React](https://codesandbox.io/s/react-mh8wui)| Configuration |
|[React Typescript](https://codesandbox.io/s/react-typescript-jg7qvh)| Configuration |
|[React Browserify](https://codesandbox.io/s/react-browserify-ts871s)| Configuration |
|[React Snowpack](https://codesandbox.io/s/react-snowpack-ju3hng)| Configuration |
|[React Parcel](https://codesandbox.io/s/react-parcel-yk2pct)| Configuration |
|[Next.js](https://codesandbox.io/s/next-js-srxqm0)| Configuration |


Patch:
diff --git a/docs/typescript.md b/docs/typescript.md
index cb7e369..cfa6cc2 100644
--- a/docs/typescript.md
+++ b/docs/typescript.md
@@ -190,7 +190,7 @@ If you're eager to know what the answer is to this particular problem then it's
 ### Middleware that does not change the store type
 
 ```ts
-import create, { State, StateCreator, StoreMutatorIdentifier, Mutate, StoreApi } from "zustand"
+import create, { State, StateCreator, StoreMutatorIdentifier } from "zustand"
 
 type Logger =
   < T extends State
@@ -296,7 +296,7 @@ console.log(store.foo.toUpperCase())
 The recommended way to use `create` is using the curried workaround ie `create<T>()(...)` because this enabled you to infer the store type. But for some reason if you don't want to use the workaround then you can pass the type parameters like the following. Note that in some cases this acts as an assertion instead of annotation, so it's not recommended.
 
 ```ts
-import create, { State, StateCreator, StoreMutatorIdentifier, Mutate, StoreApi } from "zustand"
+import create from "zustand"
 
 interface BearState {
   bears: number
@@ -318,7 +318,7 @@ const useStore = create<
 ### Independent slices pattern
 
 ```ts
-import create, { State, StateCreator, StoreMutatorIdentifier, Mutate, StoreApi } from "zustand"
+import create, { StateCreator } from "zustand"
 
 interface BearSlice {
   bears: number
@@ -351,7 +351,7 @@ Also you can even write `StateCreator<MySlice>` instead of `StateCreator<MySlice
 ### Interdependent slices pattern
 
 ```ts
-import create, { State, StateCreator, StoreMutatorIdentifier, Mutate, StoreApi } from "zustand"
+import create, { StateCreator } from "zustand"
 
 interface BearSlice {
   bears: number
diff --git a/src/middleware/devtools.ts b/src/middleware/devtools.ts
index 144458a..34b7e3b 100644
--- a/src/middleware/devtools.ts
+++ b/src/middleware/devtools.ts
@@ -58,8 +58,8 @@ type StoreSetStateWithAction<S> = S extends {
 
 interface DevtoolsOptions {
   enabled?: boolean
-  name?: string
   anonymousActionType?: string
+  name?: string
   serialize?:
     | boolean
     | {
@@ -81,7 +81,7 @@ type Devtools = <
   Mcs extends [StoreMutatorIdentifier, unknown][] = []
 >(
   initializer: StateCreator<T, [...Mps, ['zustand/devtools', never]], Mcs>,
-  options?: DevtoolsOptions
+  devtoolsOptions?: DevtoolsOptions
 ) => StateCreator<T, Mps, [['zustand/devtools', never], ...Mcs]>
 
 declare module '../vanilla' {
@@ -93,7 +93,7 @@ declare module '../vanilla' {
 
 type DevtoolsImpl = <T extends State>(
   storeInitializer: PopArgument<StateCreator<T, [], []>>,
-  options: DevtoolsOptions
+  devtoolsOptions?: DevtoolsOptions
 ) => PopArgument<StateCreator<T, [], []>>
 
 type PopArgument<T extends (...a: never[]) => unknown> = T extends (
@@ -104,155 +104,152 @@ type PopArgument<T extends (...a: never[]) => unknown> = T extends (
 
 export type NamedSet<T extends State> = WithDevtools<StoreApi<T>>['setState']
 
-const devtoolsImpl: DevtoolsImpl = (fn, options) => (set, get, api) => {
-  type S = ReturnType<typeof fn>
-
-  const devtoolsOptions =
-    options === undefined
-      ? {}
-      : typeof options === 'string'
-      ? { name: options }
-      : options
-
-  const { enabled } = devtoolsOptions as { enabled?: boolean }
-  let extensionConnector: typeof window['__REDUX_DEVTOOLS_EXTENSION__'] | false
-  try {
-    extensionConnector =
-      (enabled ?? __DEV__) && window.__REDUX_DEVTOOLS_EXTENSION__
-  } catch {
-    // ignored
-  }
+const devtoolsImpl: DevtoolsImpl =
+  (fn, devtoolsOptions = {}) =>
+  (set, get, api) => {
+    type S = ReturnType<typeof fn>
 
-  if (!extensionConnector) {
-    if (__DEV__ && enabled) {
-      console.warn(
-        '[zustand devtools middleware] Please install/enable Redux devtools extension'
-      )
+    const { enabled, anonymousActionType, ...options } = devtoolsOptions
+    let extensionConnector:
+      | typeof window['__REDUX_DEVTOOLS_EXTENSION__']
+      | false
+    try {
+      extensionConnector =
+        (enabled ?? __DEV__) && window.__REDUX_DEVTOOLS_EXTENSION__
+    } catch {
+      // ignored
     }
-    return fn(set, get, api)
-  }
-
-  const extension = extensionConnector.connect(devtoolsOptions)
-
-  let isRecording = true
-  ;(api.setState as NamedSet<S>) = (state, replace, nameOrAction) => {
-    const r = set(state, replace)
-    if (!isRecording) return r
-    extension.send(
-      nameOrAction === undefined
-        ? { type: devtoolsOptions.anonymousActionType || 'anonymous' }
-        : typeof nameOrAction === 'string'
-        ? { type: nameOrAction }
-        : nameOrAction,
-      get()
-    )
-    return r
-  }
-  const setStateFromDevtools: SetState<S> = (...a) => {
-    const originalIsRecording = isRecording
-    isRecording = false
-    set(...a)
-    isRecording = originalIsRecording
-  }
-
-  const initialState = fn(api.setState, get, api)
-  extension.init(initialState)
 
-  if (
-    (api as any).dispatchFromDevtools &&
-    typeof (api as any).dispatch === 'function'
-  ) {
-    let didWarnAboutReservedActionType = false
-    const originalDispatch = (api as any).dispatch
-    ;(api as any).dispatch = (...a: any[]) => {
-      if (
-        __DEV__ &&
-        a[0].type === '__setState' &&
-        !didWarnAboutReservedActionType
-      ) {
+    if (!extensionConnector) {
+      if (__DEV__ && enabled) {
         console.warn(
-          '[zustand devtools middleware] "__setState" action type is reserved ' +
-            'to set state from the devtools. Avoid using it.'
+          '[zustand devtools middleware] Please install/enable Redux devtools extension'
         )
-        didWarnAboutReservedActionType = true
       }
-      ;(originalDispatch as any)(...a)
+      return fn(set, get, api)
     }
-  }
 
-  ;(
-    extension as unknown as {
-      // FIXME https://github.com/reduxjs/redux-devtools/issues/1097
-      subscribe: (
-        listener: (message: Message) => void
-      ) => (() => void) | undefined
+    const extension = extensionConnector.connect(options)
+
+    let isRecording = true
+    ;(api.setState as NamedSet<S>) = (state, replace, nameOrAction) => {
+      const r = set(state, replace)
+      if (!isRecording) return r
+      extension.send(
+        nameOrAction === undefined
+          ? { type: anonymousActionType || 'anonymous' }
+          : typeof nameOrAction === 'string'
+          ? { type: nameOrAction }
+          : nameOrAction,
+        get()
+      )
+      return r
     }
-  ).subscribe((message: any) => {
-    switch (message.type) {
-      case 'ACTION':
-        if (typeof message.payload !== 'string') {
-          console.error(
-            '[zustand devtools middleware] Unsupported action format'
+    const setStateFromDevtools: SetState<S> = (...a) => {
+      const originalIsRecording = isRecording
+      isRecording = false
+      set(...a)
+      isRecording = originalIsRecording
+    }
+
+    const initialState = fn(api.setState, get, api)
+    extension.init(initialState)
+
+    if (
+      (api as any).dispatchFromDevtools &&
+      typeof (api as any).dispatch === 'function'
+    ) {
+      let didWarnAboutReservedActionType = false
+      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.'
           )
-          return
+          didWarnAboutReservedActionType = true
         }
-        return parseJsonThen<{ type: unknown; state?: PartialState<S> }>(
-          message.payload,
-          (action) => {
-            if (action.type === '__setState') {
-              setStateFromDevtools(action.state as PartialState<S>)
-              return
-            }
+        ;(originalDispatch as any)(...a)
+      }
+    }
 
-            if (!(api as any).dispatchFromDevtools) return
-            if (typeof (api as any).dispatch !== 'function') return
-            ;(api as any).dispatch(action)
+    ;(
+      extension as unknown as {
+        // FIXME https://github.com/reduxjs/redux-devtools/issues/1097
+        subscribe: (
+          listener: (message: Message) => void
+        ) => (() => void) | undefined
+      }
+    ).subscribe((message: any) => {
+      switch (message.type) {
+        case 'ACTION':
+          if (typeof message.payload !== 'string') {
+            console.error(
+              '[zustand devtools middleware] Unsupported action format'
+            )
+            return
           }
-        )
+          return parseJsonThen<{ type: unknown; state?: PartialState<S> }>(
+            message.payload,
+            (action) => {
+              if (action.type === '__setState') {
+                setStateFromDevtools(action.state as PartialState<S>)
+                return
+              }
 
-      case 'DISPATCH':
-        switch (message.payload.type) {
-          case 'RESET':
-            setStateFromDevtools(initialState)
-            return extension.init(api.getState())
+              if (!(api as any).dispatchFromDevtools) return
+              if (typeof (api as any).dispatch !== 'function') return
+              ;(api as any).dispatch(action)
+            }
+          )
 
-          case 'COMMIT':
-            return extension.init(api.getState())
+        case 'DISPATCH':
+          switch (message.payload.type) {
+            case 'RESET':
+              setStateFromDevtools(initialState)
+              return extension.init(api.getState())
 
-          case 'ROLLBACK':
-            return parseJsonThen<S>(message.state, (state) => {
-              setStateFromDevtools(state)
-              extension.init(api.getState())
-            })
+            case 'COMMIT':
+              return extension.init(api.getState())
 
-          case 'JUMP_TO_STATE':
-          case 'JUMP_TO_ACTION':
-            return parseJsonThen<S>(message.state, (state) => {
-              setStateFromDevtools(state)
-            })
+            case 'ROLLBACK':
+              return parseJsonThen<S>(message.state, (state) => {
+                setStateFromDevtools(state)
+                extension.init(api.getState())
+              })
 
-          case 'IMPORT_STATE': {
-            const { nextLiftedState } = message.payload
-            const lastComputedState =
-              nextLiftedState.computedStates.slice(-1)[0]?.state
-            if (!lastComputedState) return
-            setStateFromDevtools(lastComputedState)
-            extension.send(
-              null as any, // FIXME no-any
-              nextLiftedState
-            )
-            return
-          }
+            case 'JUMP_TO_STATE':
+            case 'JUMP_TO_ACTION':
+              return parseJsonThen<S>(message.state, (state) => {
+                setStateFromDevtools(state)
+              })
 
-          case 'PAUSE_RECORDING':
-            return (isRecording = !isRecording)
-        }
-        return
-    }
-  })
+            case 'IMPORT_STATE': {
+              const { nextLiftedState } = message.payload
+              const lastComputedState =
+                nextLiftedState.computedStates.slice(-1)[0]?.state
+              if (!lastComputedState) return
+              setStateFromDevtools(lastComputedState)
+              extension.send(
+                null as any, // FIXME no-any
+                nextLiftedState
+              )
+              return
+            }
 
-  return initialState
-}
+            case 'PAUSE_RECORDING':
+              return (isRecording = !isRecording)
+          }
+          return
+      }
+    })
+
+    return initialState
+  }
 export const devtools = devtoolsImpl as unknown as Devtools
 
 const parseJsonThen = <T>(stringified: string, f: (parsed: T) => void) => {


Test patch:
diff --git a/tests/devtools.test.tsx b/tests/devtools.test.tsx
index f5ffb5b..1b39612 100644
--- a/tests/devtools.test.tsx
+++ b/tests/devtools.test.tsx
@@ -26,9 +26,9 @@ beforeEach(() => {
 })
 
 it('connects to the extension by passing the options and initializes', () => {
-  const options = { name: 'test', foo: 'bar', enabled: true }
+  const options = { name: 'test', foo: 'bar' }
   const initialState = { count: 0 }
-  create(devtools(() => initialState, options))
+  create(devtools(() => initialState, { enabled: true, ...options }))
 
   expect(extensionConnector.connect).toHaveBeenLastCalledWith(options)
   expect(extension.init).toHaveBeenLastCalledWith(initialState)