Skip to content

Commit e5bcf28

Browse files
committed
Replace zustand with jotai
1 parent b3ac2db commit e5bcf28

17 files changed

+400
-311
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"@emotion/styled": "^11.10.6",
6666
"@mui/material": "^5.11.13",
6767
"copy-to-clipboard": "^3.3.3",
68-
"zustand": "^4.3.6"
68+
"jotai": "^1.13.0"
6969
},
7070
"lint-staged": {
7171
"!*.{ts,tsx,js,jsx}": "prettier --write --ignore-unknown",

rollup.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ const external = [
2929
'@mui/material',
3030
'@mui/material/styles',
3131
'copy-to-clipboard',
32-
'zustand',
32+
'jotai',
33+
'jotai/utils',
3334
'react',
3435
'react/jsx-runtime',
3536
'react-dom',

src/components/DataKeyPair.tsx

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
import { Box } from '@mui/material'
2+
import { useAtomValue, useSetAtom } from 'jotai'
3+
import { useAtomCallback } from 'jotai/utils'
24
import type { ComponentProps, FC, MouseEvent } from 'react'
35
import { useCallback, useMemo, useState } from 'react'
46

5-
import { useTextColor } from '../hooks/useColor'
67
import { useClipboard } from '../hooks/useCopyToClipboard'
78
import { useInspect } from '../hooks/useInspect'
8-
import { useJsonViewerStore } from '../stores/JsonViewerStore'
9+
import {
10+
colorspaceAtom,
11+
editableAtom,
12+
enableClipboardAtom,
13+
hoverPathAtom,
14+
keyRendererAtom,
15+
onChangeAtom,
16+
quotesOnKeysAtom,
17+
rootNameAtom,
18+
setHoverAtomFamily,
19+
valueAtom
20+
} from '../state'
921
import { useTypeComponents } from '../stores/typeRegistry'
1022
import type { DataItemProps } from '../type'
1123
import { getValueSize } from '../utils'
@@ -43,7 +55,7 @@ const IconBox: FC<IconBoxProps> = (props) => (
4355
export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
4456
const { value, path, nestedIndex } = props
4557
const propsEditable = props.editable ?? undefined
46-
const storeEditable = useJsonViewerStore(store => store.editable)
58+
const storeEditable = useAtomValue(editableAtom)
4759
const editable = useMemo(() => {
4860
if (storeEditable === false) {
4961
return false
@@ -60,26 +72,33 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
6072
const [tempValue, setTempValue] = useState(typeof value === 'function' ? () => value : value)
6173
const depth = path.length
6274
const key = path[depth - 1]
63-
const hoverPath = useJsonViewerStore(store => store.hoverPath)
75+
const hoverPath = useAtomValue(hoverPathAtom)
6476
const isHover = useMemo(() => {
6577
return hoverPath && path.every(
6678
(value, index) => value === hoverPath.path[index] && nestedIndex ===
6779
hoverPath.nestedIndex)
6880
}, [hoverPath, path, nestedIndex])
69-
const setHover = useJsonViewerStore(store => store.setHover)
70-
const root = useJsonViewerStore(store => store.value)
81+
const setHover = useAtomCallback(
82+
useCallback((get, set, arg) => {
83+
// eslint-disable-next-line react-hooks/rules-of-hooks
84+
useSetAtom(setHoverAtomFamily(arg))
85+
}, [])
86+
)
87+
const root = useAtomValue(valueAtom)
7188
const [inspect, setInspect] = useInspect(path, value, nestedIndex)
7289
const [editing, setEditing] = useState(false)
73-
const onChange = useJsonViewerStore(store => store.onChange)
74-
const keyColor = useTextColor()
75-
const numberKeyColor = useJsonViewerStore(store => store.colorspace.base0C)
90+
const onChange = useAtomValue(onChangeAtom)
91+
const {
92+
base07: keyColor,
93+
base0C: numberKeyColor
94+
} = useAtomValue(colorspaceAtom)
7695
const { Component, PreComponent, PostComponent, Editor } = useTypeComponents(value, path)
77-
const quotesOnKeys = useJsonViewerStore(store => store.quotesOnKeys)
78-
const rootName = useJsonViewerStore(store => store.rootName)
96+
const quotesOnKeys = useAtomValue(quotesOnKeysAtom)
97+
const rootName = useAtomValue(rootNameAtom)
7998
const isRoot = root === value
8099
const isNumberKey = Number.isInteger(Number(key))
81100

82-
const enableClipboard = useJsonViewerStore(store => store.enableClipboard)
101+
const enableClipboard = useAtomValue(enableClipboardAtom)
83102
const { copy, copied } = useClipboard()
84103

85104
const actionIcons = useMemo(() => {
@@ -161,7 +180,7 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
161180

162181
const isEmptyValue = useMemo(() => getValueSize(value) === 0, [value])
163182
const expandable = !isEmptyValue && !!(PreComponent && PostComponent)
164-
const KeyRenderer = useJsonViewerStore(store => store.keyRenderer)
183+
const KeyRenderer = useAtomValue(keyRendererAtom)
165184
const downstreamProps: DataItemProps = useMemo(() => ({
166185
path,
167186
inspect,
@@ -173,10 +192,7 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
173192
className='data-key-pair'
174193
data-testid={'data-key-pair' + path.join('.')}
175194
sx={{ userSelect: 'text' }}
176-
onMouseEnter={
177-
useCallback(() => setHover(path, nestedIndex),
178-
[setHover, path, nestedIndex])
179-
}
195+
onMouseEnter={() => setHover({ path, nestedIndex })}
180196
>
181197
<DataBox
182198
component='span'

src/components/DataTypes/Function.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
* Because in Next.js SSR, the function will be translated to other type
44
*/
55
import { Box, NoSsr } from '@mui/material'
6+
import { useAtomValue } from 'jotai'
67
import type { FC } from 'react'
78

8-
import { useJsonViewerStore } from '../../stores/JsonViewerStore'
9+
import { colorspaceAtom } from '../../state'
910
import type { DataItemProps } from '../../type'
1011
import { DataTypeLabel } from '../DataTypeLabel'
1112

@@ -70,7 +71,7 @@ export const PostFunctionType: FC<DataItemProps<Function>> = () => {
7071
}
7172

7273
export const FunctionType: FC<DataItemProps<Function>> = (props) => {
73-
const functionColor = useJsonViewerStore(store => store.colorspace.base05)
74+
const { base05: functionColor } = useAtomValue(colorspaceAtom)
7475
return (
7576
<NoSsr>
7677
<Box

src/components/DataTypes/Object.tsx

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import { Box } from '@mui/material'
2+
import { useAtomValue } from 'jotai'
23
import type { FC } from 'react'
34
import { useMemo, useState } from 'react'
45

5-
import { useTextColor } from '../../hooks/useColor'
66
import { useIsCycleReference } from '../../hooks/useIsCycleReference'
7-
import { useJsonViewerStore } from '../../stores/JsonViewerStore'
7+
import {
8+
colorspaceAtom,
9+
displayObjectSizeAtom,
10+
groupArraysAfterLengthAtom,
11+
indentWidthAtom,
12+
maxDisplayLengthAtom,
13+
objectSortKeysAtom
14+
} from '../../state'
815
import type { DataItemProps } from '../../type'
916
import { getValueSize, segmentArray } from '../../utils'
1017
import { DataKeyPair } from '../DataKeyPair'
@@ -30,13 +37,14 @@ function inspectMetadata (value: object) {
3037
}
3138

3239
export const PreObjectType: FC<DataItemProps<object>> = (props) => {
33-
const metadataColor = useJsonViewerStore(store => store.colorspace.base04)
34-
const textColor = useTextColor()
40+
const {
41+
base04: metadataColor,
42+
base07: textColor
43+
} = useAtomValue(colorspaceAtom)
3544
const isArray = useMemo(() => Array.isArray(props.value), [props.value])
3645
const isEmptyValue = useMemo(() => getValueSize(props.value) === 0, [props.value])
37-
const sizeOfValue = useMemo(() => inspectMetadata(props.value), [props.value]
38-
)
39-
const displayObjectSize = useJsonViewerStore(store => store.displayObjectSize)
46+
const sizeOfValue = useMemo(() => inspectMetadata(props.value), [props.inspect, props.value])
47+
const displayObjectSize = useAtomValue(displayObjectSizeAtom)
4048
const isTrap = useIsCycleReference(props.path, props.value)
4149
return (
4250
<Box
@@ -78,9 +86,9 @@ export const PreObjectType: FC<DataItemProps<object>> = (props) => {
7886
}
7987

8088
export const PostObjectType: FC<DataItemProps<object>> = (props) => {
81-
const metadataColor = useJsonViewerStore(store => store.colorspace.base04)
89+
const { base04: metadataColor } = useAtomValue(colorspaceAtom)
8290
const isArray = useMemo(() => Array.isArray(props.value), [props.value])
83-
const displayObjectSize = useJsonViewerStore(store => store.displayObjectSize)
91+
const displayObjectSize = useAtomValue(displayObjectSizeAtom)
8492
const isEmptyValue = useMemo(() => getValueSize(props.value) === 0, [props.value])
8593
const sizeOfValue = useMemo(() => inspectMetadata(props.value), [props.value])
8694

@@ -111,12 +119,14 @@ function getIterator (value: any): value is Iterable<unknown> {
111119
}
112120

113121
export const ObjectType: FC<DataItemProps<object>> = (props) => {
114-
const keyColor = useTextColor()
115-
const borderColor = useJsonViewerStore(store => store.colorspace.base02)
116-
const groupArraysAfterLength = useJsonViewerStore(store => store.groupArraysAfterLength)
122+
const {
123+
base02: borderColor,
124+
base07: keyColor
125+
} = useAtomValue(colorspaceAtom)
126+
const groupArraysAfterLength = useAtomValue(groupArraysAfterLengthAtom)
117127
const isTrap = useIsCycleReference(props.path, props.value)
118-
const [displayLength, setDisplayLength] = useState(useJsonViewerStore(store => store.maxDisplayLength))
119-
const objectSortKeys = useJsonViewerStore(store => store.objectSortKeys)
128+
const [displayLength, setDisplayLength] = useState(useAtomValue(maxDisplayLengthAtom))
129+
const objectSortKeys = useAtomValue(objectSortKeysAtom)
120130
const elements = useMemo(() => {
121131
if (!props.inspect) {
122132
return null
@@ -245,7 +255,7 @@ export const ObjectType: FC<DataItemProps<object>> = (props) => {
245255
objectSortKeys
246256
])
247257
const marginLeft = props.inspect ? 0.6 : 0
248-
const width = useJsonViewerStore(store => store.indentWidth)
258+
const width = useAtomValue(indentWidthAtom)
249259
const indentWidth = props.inspect ? width - marginLeft : width
250260
const isEmptyValue = useMemo(() => getValueSize(props.value) === 0, [props.value])
251261
if (isEmptyValue) {

src/components/DataTypes/createEasyType.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { InputBase } from '@mui/material'
2+
import { useAtomValue } from 'jotai'
23
import type { ChangeEventHandler, ComponentType, FC } from 'react'
34
import { memo, useCallback } from 'react'
45

5-
import { useJsonViewerStore } from '../../stores/JsonViewerStore'
6+
import { colorspaceAtom, displayDataTypesAtom, onSelectAtom } from '../../state'
67
import type { Colorspace } from '../../theme/base16'
78
import type { DataItemProps, DataType, EditorProps } from '../../type'
89
import { DataTypeLabel } from '../DataTypeLabel'
@@ -18,13 +19,11 @@ export function createEasyType<Value> (
1819
}
1920
): Omit<DataType<Value>, 'is'> {
2021
const { fromString, colorKey, displayTypeLabel = true } = config
21-
2222
const Render = memo(renderValue)
2323
const EasyType: FC<DataItemProps<Value>> = (props) => {
24-
const storeDisplayDataTypes = useJsonViewerStore(store => store.displayDataTypes)
25-
const color = useJsonViewerStore(store => store.colorspace[colorKey])
26-
const onSelect = useJsonViewerStore(store => store.onSelect)
27-
24+
const storeDisplayDataTypes = useAtomValue(displayDataTypesAtom)
25+
const color = useAtomValue(colorspaceAtom)[colorKey]
26+
const onSelect = useAtomValue(onSelectAtom)
2827
return (
2928
<DataBox onClick={() => onSelect?.(props.path, props.value)} sx={{ color }}>
3029
{(displayTypeLabel && storeDisplayDataTypes) && <DataTypeLabel dataType={type} />}
@@ -41,9 +40,8 @@ export function createEasyType<Value> (
4140
Component: EasyType
4241
}
4342
}
44-
4543
const EasyTypeEditor: FC<EditorProps<Value>> = ({ value, setValue }) => {
46-
const color = useJsonViewerStore(store => store.colorspace[colorKey])
44+
const color = useAtomValue(colorspaceAtom)[colorKey]
4745
return (
4846
<InputBase
4947
value={value}

src/hooks/useColor.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/hooks/useCopyToClipboard.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import copyToClipboard from 'copy-to-clipboard'
2+
import { useAtomValue } from 'jotai'
23
import { useCallback, useRef, useState } from 'react'
34

4-
import { useJsonViewerStore } from '../stores/JsonViewerStore'
5+
import { onCopyAtom } from '../state'
56
import type { JsonViewerOnCopy } from '../type'
67
import { safeStringify } from '../utils'
78

@@ -23,7 +24,7 @@ export function useClipboard ({ timeout = 2000 } = {}) {
2324
copyTimeout.current = window.setTimeout(() => setCopied(false), timeout)
2425
setCopied(value)
2526
}, [timeout])
26-
const onCopy = useJsonViewerStore(store => store.onCopy)
27+
const onCopy = useAtomValue(onCopyAtom)
2728

2829
const copy = useCallback<JsonViewerOnCopy>((path, value: unknown) => {
2930
if (typeof onCopy === 'function') {

src/hooks/useInspect.ts

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,52 @@
1-
import type {
2-
Dispatch,
3-
SetStateAction
4-
} from 'react'
1+
import { useAtomValue, useSetAtom } from 'jotai'
2+
import { useAtomCallback } from 'jotai/utils'
53
import {
64
useCallback,
75
useEffect,
86
useState
97
} from 'react'
108

119
import {
12-
useJsonViewerStore
13-
} from '../stores/JsonViewerStore'
10+
defaultInspectDepthAtom,
11+
getInspectCacheAtomFamily,
12+
setInspectCacheAtomFamily
13+
} from '../state'
1414
import { useIsCycleReference } from './useIsCycleReference'
1515

1616
export function useInspect (path: (string | number)[], value: any, nestedIndex?: number) {
1717
const depth = path.length
1818
const isTrap = useIsCycleReference(path, value)
19-
const getInspectCache = useJsonViewerStore(store => store.getInspectCache)
20-
const setInspectCache = useJsonViewerStore(store => store.setInspectCache)
21-
const defaultInspectDepth = useJsonViewerStore(store => store.defaultInspectDepth)
19+
const defaultInspectDepth = useAtomValue(defaultInspectDepthAtom)
20+
21+
const getInspectCache = useAtomCallback(
22+
useCallback((get, set, arg) => {
23+
// eslint-disable-next-line react-hooks/rules-of-hooks
24+
useAtomValue(getInspectCacheAtomFamily(arg))
25+
}, [])
26+
)
27+
const setInspectCache = useAtomCallback(
28+
useCallback((get, set, arg) => {
29+
// eslint-disable-next-line react-hooks/rules-of-hooks
30+
useSetAtom(setInspectCacheAtomFamily(arg))
31+
}, [])
32+
)
2233
useEffect(() => {
23-
const inspect = getInspectCache(path, nestedIndex)
34+
const inspect = getInspectCache({ path, nestedIndex })
2435
if (inspect !== undefined) {
2536
return
2637
}
2738
if (nestedIndex !== undefined) {
28-
setInspectCache(path, false, nestedIndex)
39+
setInspectCache({ path, action: false, nestedIndex })
2940
} else {
3041
// do not inspect when it is a cycle reference, otherwise there will have a loop
3142
const inspect = isTrap
3243
? false
3344
: depth < defaultInspectDepth
34-
setInspectCache(path, inspect)
45+
setInspectCache({ path, inspect })
3546
}
36-
}, [defaultInspectDepth, depth, getInspectCache, isTrap, nestedIndex, path, setInspectCache])
37-
const [inspect, set] = useState<boolean>(() => {
38-
const shouldInspect = getInspectCache(path, nestedIndex)
47+
}, [defaultInspectDepth, depth, isTrap, nestedIndex, path, getInspectCache, setInspectCache])
48+
const shouldInspect = useAtomValue(getInspectCacheAtomFamily({ path, nestedIndex }))
49+
const [inspect, setOriginal] = useState<boolean>(() => {
3950
if (shouldInspect !== undefined) {
4051
return shouldInspect
4152
}
@@ -46,12 +57,15 @@ export function useInspect (path: (string | number)[], value: any, nestedIndex?:
4657
? false
4758
: depth < defaultInspectDepth
4859
})
49-
const setInspect = useCallback<Dispatch<SetStateAction<boolean>>>((apply) => {
50-
set((oldState) => {
51-
const newState = typeof apply === 'boolean' ? apply : apply(oldState)
52-
setInspectCache(path, newState, nestedIndex)
53-
return newState
54-
})
55-
}, [nestedIndex, path, setInspectCache])
60+
const setInspect = useAtomCallback(
61+
useCallback((get, set, apply) => {
62+
setOriginal((oldState) => {
63+
const newState = typeof apply === 'boolean' ? apply : apply(oldState)
64+
// eslint-disable-next-line react-hooks/rules-of-hooks
65+
useSetAtom(setInspectCacheAtomFamily(apply))
66+
return newState
67+
})
68+
}, [])
69+
)
5670
return [inspect, setInspect] as const
5771
}

src/hooks/useIsCycleReference.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import { useAtomValue } from 'jotai'
12
import { useMemo } from 'react'
23

3-
import { useJsonViewerStore } from '../stores/JsonViewerStore'
4+
import { valueAtom } from '../state'
45
import { isCycleReference } from '../utils'
56

67
export function useIsCycleReference (path: (string | number)[], value: any) {
7-
const rootValue = useJsonViewerStore(store => store.value)
8+
const rootValue = useAtomValue(valueAtom)
89
return useMemo(
910
() => isCycleReference(rootValue, path, value),
10-
[path, value, rootValue])
11+
[path, value, rootValue]
12+
)
1113
}

0 commit comments

Comments
 (0)