diff --git a/package.json b/package.json index b8afecf1..228580c8 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@emotion/styled": "^11.10.6", "@mui/material": "^5.11.12", "copy-to-clipboard": "^3.3.3", - "zustand": "^4.1.5" + "zustand": "^4.3.6" }, "lint-staged": { "!*.{ts,tsx,js,jsx}": "prettier --write --ignore-unknown", diff --git a/rollup.config.ts b/rollup.config.ts index a49f3c0f..3ffc69a7 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -30,8 +30,6 @@ const external = [ '@mui/material/styles', 'copy-to-clipboard', 'zustand', - 'zustand/context', - 'zustand/middleware', 'react', 'react/jsx-runtime', 'react-dom', diff --git a/src/index.tsx b/src/index.tsx index f8dd246a..b62afb84 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,19 +3,20 @@ import { ThemeProvider } from '@mui/material' import type { FC, ReactElement } from 'react' -import { useCallback, useEffect, useMemo, useRef } from 'react' +import { useCallback, useContext, useEffect, useMemo, useRef } from 'react' import { DataKeyPair } from './components/DataKeyPair' import { useThemeDetector } from './hooks/useThemeDetector' import { createJsonViewerStore, - JsonViewerProvider, - useJsonViewerStore, - useJsonViewerStoreApi + JsonViewerStoreContext, + useJsonViewerStore } from './stores/JsonViewerStore' import { - createTypeRegistryStore, predefined, - TypeRegistryProvider, useTypeRegistryStore + createTypeRegistryStore, + predefined, + TypeRegistryStoreContext, + useTypeRegistryStore } from './stores/typeRegistry' import { darkColorspace, lightColorspace } from './theme/base16' import type { JsonViewerProps } from './type' @@ -30,21 +31,21 @@ function useSetIfNotUndefinedEffect ( key: Key, value: JsonViewerProps[Key] | undefined ) { - const api = useJsonViewerStoreApi() + const { setState } = useContext(JsonViewerStoreContext) useEffect(() => { if (value !== undefined) { - api.setState({ + setState({ [key]: value }) } - }, [key, value, api]) + }, [key, value, setState]) } /** * @internal */ const JsonViewerInner: FC = (props) => { - const api = useJsonViewerStoreApi() + const { setState } = useContext(JsonViewerStoreContext) useSetIfNotUndefinedEffect('value', props.value) useSetIfNotUndefinedEffect('editable', props.editable) useSetIfNotUndefinedEffect('indentWidth', props.indentWidth) @@ -60,19 +61,19 @@ const JsonViewerInner: FC = (props) => { useSetIfNotUndefinedEffect('onSelect', props.onSelect) useEffect(() => { if (props.theme === 'light') { - api.setState({ + setState({ colorspace: lightColorspace }) } else if (props.theme === 'dark') { - api.setState({ + setState({ colorspace: darkColorspace }) } else if (typeof props.theme === 'object') { - api.setState({ + setState({ colorspace: props.theme }) } - }, [api, props.theme]) + }, [setState, props.theme]) const onceRef = useRef(true) const predefinedTypes = useMemo(() => predefined(), []) const registerTypes = useTypeRegistryStore(store => store.registerTypes) @@ -143,17 +144,18 @@ export const JsonViewer = function JsonViewer (props: JsonViewerProps createJsonViewerStore(props), []) + const typeRegistryStore = useMemo(() => createTypeRegistryStore(), []) + return ( - - { - // This function only runs once, so we don't need a memo for this. - // Refs: https://github.com/pmndrs/zustand/blob/77d14b17bc33a6f10f072802fac56aa78510710e/src/context.ts#L36-L38 - return createJsonViewerStore(props) - }}> + + - - + + ) } diff --git a/src/stores/JsonViewerStore.ts b/src/stores/JsonViewerStore.ts index a995b7b4..92f3a85f 100644 --- a/src/stores/JsonViewerStore.ts +++ b/src/stores/JsonViewerStore.ts @@ -1,7 +1,7 @@ import type { SetStateAction } from 'react' -import create from 'zustand' -import createContext from 'zustand/context' -import { combine } from 'zustand/middleware' +import { createContext, useContext } from 'react' +import type { StoreApi } from 'zustand' +import { create, useStore } from 'zustand' import type { JsonViewerOnChange, @@ -38,85 +38,78 @@ export type JsonViewerState = { onSelect: JsonViewerOnSelect | undefined keyRenderer: JsonViewerKeyRenderer displayObjectSize: boolean -} -export type JsonViewerActions = { getInspectCache: (path: Path, nestedIndex?: number) => boolean setInspectCache: ( path: Path, action: SetStateAction, nestedIndex?: number) => void setHover: (path: Path | null, nestedIndex?: number) => void } -export const createJsonViewerStore = (props: JsonViewerProps) => - create( - combine, JsonViewerActions>( - { - // provided by user - enableClipboard: props.enableClipboard ?? true, - indentWidth: props.indentWidth ?? 3, - groupArraysAfterLength: props.groupArraysAfterLength ?? 100, - collapseStringsAfterLength: - (props.collapseStringsAfterLength === false) - ? Number.MAX_VALUE - : props.collapseStringsAfterLength ?? 50, - maxDisplayLength: props.maxDisplayLength ?? 30, - rootName: props.rootName ?? 'root', - onChange: props.onChange ?? (() => {}), - onCopy: props.onCopy ?? undefined, - onSelect: props.onSelect ?? undefined, - keyRenderer: props.keyRenderer ?? DefaultKeyRenderer, - editable: props.editable ?? false, - defaultInspectDepth: props.defaultInspectDepth ?? 5, - objectSortKeys: props.objectSortKeys ?? false, - quotesOnKeys: props.quotesOnKeys ?? true, - displayDataTypes: props.displayDataTypes ?? true, - // internal state - inspectCache: {}, - hoverPath: null, - colorspace: lightColorspace, - value: props.value, - displayObjectSize: props.displayObjectSize ?? true - }, - (set, get) => ({ - getInspectCache: (path, nestedIndex) => { - const target = nestedIndex !== undefined - ? path.join('.') + +export const createJsonViewerStore = (props: JsonViewerProps) => { + return create()((set, get) => ({ + // provided by user + enableClipboard: props.enableClipboard ?? true, + indentWidth: props.indentWidth ?? 3, + groupArraysAfterLength: props.groupArraysAfterLength ?? 100, + collapseStringsAfterLength: + (props.collapseStringsAfterLength === false) + ? Number.MAX_VALUE + : props.collapseStringsAfterLength ?? 50, + maxDisplayLength: props.maxDisplayLength ?? 30, + rootName: props.rootName ?? 'root', + onChange: props.onChange ?? (() => {}), + onCopy: props.onCopy ?? undefined, + onSelect: props.onSelect ?? undefined, + keyRenderer: props.keyRenderer ?? DefaultKeyRenderer, + editable: props.editable ?? false, + defaultInspectDepth: props.defaultInspectDepth ?? 5, + objectSortKeys: props.objectSortKeys ?? false, + quotesOnKeys: props.quotesOnKeys ?? true, + displayDataTypes: props.displayDataTypes ?? true, + // internal state + inspectCache: {}, + hoverPath: null, + colorspace: lightColorspace, + value: props.value, + displayObjectSize: props.displayObjectSize ?? true, + + getInspectCache: (path, nestedIndex) => { + const target = nestedIndex !== undefined + ? path.join('.') + `[${nestedIndex}]nt` - : path.join('.') - return get().inspectCache[target] - }, - setInspectCache: (path, action, nestedIndex) => { - const target = nestedIndex !== undefined - ? path.join('.') + + : path.join('.') + return get().inspectCache[target] + }, + setInspectCache: (path, action, nestedIndex) => { + const target = nestedIndex !== undefined + ? path.join('.') + `[${nestedIndex}]nt` - : path.join('.') - set(state => ({ - inspectCache: { - ...state.inspectCache, - [target]: typeof action === 'function' - ? action( - state.inspectCache[target]) - : action - } - })) - }, - setHover: (path, nestedIndex) => { - set({ - hoverPath: path - ? ({ - path, - nestedIndex - }) - : null - }) + : path.join('.') + set(state => ({ + inspectCache: { + ...state.inspectCache, + [target]: typeof action === 'function' + ? action( + state.inspectCache[target]) + : action } + })) + }, + setHover: (path, nestedIndex) => { + set({ + hoverPath: path + ? ({ path, nestedIndex }) + : null }) - ) - ) -export type JsonViewerStore = ReturnType + } + })) +} -export const { - useStore: useJsonViewerStore, - useStoreApi: useJsonViewerStoreApi, - Provider: JsonViewerProvider -} = createContext() +export const JsonViewerStoreContext = createContext>(undefined) + +export const JsonViewerProvider = JsonViewerStoreContext.Provider + +export const useJsonViewerStore = (selector: (state: JsonViewerState) => U, equalityFn?: (a: U, b: U) => boolean) => { + const store = useContext(JsonViewerStoreContext) + return useStore(store, selector, equalityFn) +} diff --git a/src/stores/typeRegistry.tsx b/src/stores/typeRegistry.tsx index 7408c446..9fcdd2ef 100644 --- a/src/stores/typeRegistry.tsx +++ b/src/stores/typeRegistry.tsx @@ -1,9 +1,8 @@ import { Box } from '@mui/material' import type { SetStateAction } from 'react' -import { memo, useMemo, useState } from 'react' -import create from 'zustand' -import createStore from 'zustand/context' -import { combine } from 'zustand/middleware' +import { createContext, memo, useContext, useMemo, useState } from 'react' +import type { StoreApi } from 'zustand' +import { createStore, useStore } from 'zustand' import { createEasyType } from '../components/DataTypes/createEasyType' import { @@ -21,35 +20,33 @@ import { useJsonViewerStore } from './JsonViewerStore' type TypeRegistryState = { registry: DataType[] -} -type TypeRegistryActions = { registerTypes: (setState: SetStateAction[]>) => void } -export const createTypeRegistryStore = () => create( - combine( - { - registry: [] - }, - (set) => ({ - registerTypes: (setState) => { - set(state => ({ - registry: - typeof setState === 'function' - ? setState(state.registry) - : setState - })) - } - }) - ) -) +export const createTypeRegistryStore = () => { + return createStore()((set) => ({ + registry: [], + + registerTypes: (setState) => { + set(state => ({ + registry: + typeof setState === 'function' + ? setState(state.registry) + : setState + })) + } + })) +} + +export const TypeRegistryStoreContext = createContext>(undefined) -export const { - Provider: TypeRegistryProvider, - useStore: useTypeRegistryStore, - useStoreApi: useTypeRegistryStoreApi -} = createStore>() +export const TypeRegistryProvider = TypeRegistryStoreContext.Provider + +export const useTypeRegistryStore = (selector: (state: TypeRegistryState) => U, equalityFn?: (a: U, b: U) => boolean) => { + const store = useContext(TypeRegistryStoreContext) + return useStore(store, selector, equalityFn) +} const objectType: DataType = { is: (value) => typeof value === 'object', diff --git a/yarn.lock b/yarn.lock index c4049c53..03ac0e0c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1961,7 +1961,7 @@ __metadata: typescript: ^4.9.5 vite: ^4.1.4 vitest: ^0.29.2 - zustand: ^4.1.5 + zustand: ^4.3.6 peerDependencies: react: ^17 || ^18 react-dom: ^17 || ^18 @@ -10449,9 +10449,9 @@ __metadata: languageName: node linkType: hard -"zustand@npm:^4.1.5": - version: 4.1.5 - resolution: "zustand@npm:4.1.5" +"zustand@npm:^4.3.6": + version: 4.3.6 + resolution: "zustand@npm:4.3.6" dependencies: use-sync-external-store: 1.2.0 peerDependencies: @@ -10462,7 +10462,7 @@ __metadata: optional: true react: optional: true - checksum: 13190ee8e8a797c5347b525a7c392be62b2addacdd9645dd20d37ea053f96c7c7067c099c6201e98ebb8d54991f2e04e241cc323f9a25b841d44f0ae048e3afc + checksum: 4d3cec03526f04ff3de6dc45b6f038c47f091836af9660fbf5f682cae1628221102882df20e4048dfe699a43f67424e5d6afc1116f3838a80eea5dd4f95ddaed languageName: node linkType: hard