Skip to content

Commit 5098030

Browse files
committed
Replace zustand with jotai
1 parent f371b78 commit 5098030

17 files changed

+383
-319
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"@mui/material": "^5.11.4",
6565
"copy-to-clipboard": "^3.3.3",
6666
"group-items": "^2.2.0",
67-
"zustand": "^4.1.5"
67+
"jotai": "^1.13.0"
6868
},
6969
"lint-staged": {
7070
"!*.{ts,tsx,js,jsx}": "prettier --write --ignore-unknown",

rollup.config.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ const external = [
2929
'@mui/material',
3030
'@mui/material/styles',
3131
'copy-to-clipboard',
32-
'zustand',
33-
'zustand/context',
34-
'zustand/middleware',
32+
'jotai',
33+
'jotai/utils',
3534
'group-items',
3635
'react',
3736
'react/jsx-runtime',

src/components/DataKeyPair.tsx

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
import { Box, styled } from '@mui/material'
2+
import { useAtomValue, useSetAtom } from 'jotai'
3+
import { useAtomCallback } from 'jotai/utils'
24
import type React 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'
@@ -34,7 +46,7 @@ const IconBox = styled(props => <Box {...props} component='span'/>)`
3446
export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
3547
const { value, path, nestedIndex } = props
3648
const propsEditable = props.editable ?? undefined
37-
const storeEditable = useJsonViewerStore(store => store.editable)
49+
const storeEditable = useAtomValue(editableAtom)
3850
const editable = useMemo(() => {
3951
if (storeEditable === false) {
4052
return false
@@ -51,26 +63,33 @@ export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
5163
const [tempValue, setTempValue] = useState(typeof value === 'function' ? () => value : value)
5264
const depth = path.length
5365
const key = path[depth - 1]
54-
const hoverPath = useJsonViewerStore(store => store.hoverPath)
66+
const hoverPath = useAtomValue(hoverPathAtom)
5567
const isHover = useMemo(() => {
5668
return hoverPath && path.every(
5769
(value, index) => value === hoverPath.path[index] && nestedIndex ===
5870
hoverPath.nestedIndex)
5971
}, [hoverPath, path, nestedIndex])
60-
const setHover = useJsonViewerStore(store => store.setHover)
61-
const root = useJsonViewerStore(store => store.value)
72+
const setHover = useAtomCallback(
73+
useCallback((get, set, arg) => {
74+
// eslint-disable-next-line react-hooks/rules-of-hooks
75+
useSetAtom(setHoverAtomFamily(arg))
76+
}, [])
77+
)
78+
const root = useAtomValue(valueAtom)
6279
const [inspect, setInspect] = useInspect(path, value, nestedIndex)
6380
const [editing, setEditing] = useState(false)
64-
const onChange = useJsonViewerStore(store => store.onChange)
65-
const keyColor = useTextColor()
66-
const numberKeyColor = useJsonViewerStore(store => store.colorspace.base0C)
81+
const onChange = useAtomValue(onChangeAtom)
82+
const {
83+
base07: keyColor,
84+
base0C: numberKeyColor
85+
} = useAtomValue(colorspaceAtom)
6786
const { Component, PreComponent, PostComponent, Editor } = useTypeComponents(value, path)
68-
const quotesOnKeys = useJsonViewerStore(store => store.quotesOnKeys)
69-
const rootName = useJsonViewerStore(store => store.rootName)
87+
const quotesOnKeys = useAtomValue(quotesOnKeysAtom)
88+
const rootName = useAtomValue(rootNameAtom)
7089
const isRoot = root === value
7190
const isNumberKey = Number.isInteger(Number(key))
7291

73-
const enableClipboard = useJsonViewerStore(store => store.enableClipboard)
92+
const enableClipboard = useAtomValue(enableClipboardAtom)
7493
const { copy, copied } = useClipboard()
7594

7695
const actionIcons = useMemo(() => {
@@ -177,7 +196,7 @@ export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
177196

178197
const isEmptyValue = useMemo(() => getValueSize(value) === 0, [value])
179198
const expandable = !isEmptyValue && !!(PreComponent && PostComponent)
180-
const KeyRenderer = useJsonViewerStore(store => store.keyRenderer)
199+
const KeyRenderer = useAtomValue(keyRendererAtom)
181200
const downstreamProps: DataItemProps = useMemo(() => ({
182201
path,
183202
inspect,
@@ -187,10 +206,7 @@ export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
187206
return (
188207
<Box className='data-key-pair'
189208
data-testid={'data-key-pair' + path.join('.')}
190-
onMouseEnter={
191-
useCallback(() => setHover(path, nestedIndex),
192-
[setHover, path, nestedIndex])
193-
}
209+
onMouseEnter={() => setHover({ path, nestedIndex })}
194210
>
195211
<DataBox
196212
component='span'
@@ -209,7 +225,7 @@ export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
209225
if (!isEmptyValue) {
210226
setInspect(state => !state)
211227
}
212-
}, [setInspect])
228+
}, [isEmptyValue, setInspect])
213229
}
214230
>
215231
{

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 React 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

@@ -69,7 +70,7 @@ export const PostFunctionType: React.FC<DataItemProps<Function>> = () => {
6970
}
7071

7172
export const FunctionType: React.FC<DataItemProps<Function>> = (props) => {
72-
const functionColor = useJsonViewerStore(store => store.colorspace.base05)
73+
const { base05: functionColor } = useAtomValue(colorspaceAtom)
7374
return (
7475
<NoSsr>
7576
<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'
22
import { group } from 'group-items'
3+
import { useAtomValue } from 'jotai'
34
import React, { 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 } from '../../utils'
1017
import { DataKeyPair } from '../DataKeyPair'
@@ -30,13 +37,14 @@ function inspectMetadata (value: object) {
3037
}
3138

3239
export const PreObjectType: React.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.inspect, 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
@@ -76,9 +84,9 @@ export const PreObjectType: React.FC<DataItemProps<object>> = (props) => {
7684
}
7785

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

@@ -109,12 +117,14 @@ function getIterator (value: any): value is Iterable<unknown> {
109117
}
110118

111119
export const ObjectType: React.FC<DataItemProps<object>> = (props) => {
112-
const keyColor = useTextColor()
113-
const borderColor = useJsonViewerStore(store => store.colorspace.base02)
114-
const groupArraysAfterLength = useJsonViewerStore(store => store.groupArraysAfterLength)
120+
const {
121+
base02: borderColor,
122+
base07: keyColor
123+
} = useAtomValue(colorspaceAtom)
124+
const groupArraysAfterLength = useAtomValue(groupArraysAfterLengthAtom)
115125
const isTrap = useIsCycleReference(props.path, props.value)
116-
const [displayLength, setDisplayLength] = useState(useJsonViewerStore(store => store.maxDisplayLength))
117-
const objectSortKeys = useJsonViewerStore(store => store.objectSortKeys)
126+
const [displayLength, setDisplayLength] = useState(useAtomValue(maxDisplayLengthAtom))
127+
const objectSortKeys = useAtomValue(objectSortKeysAtom)
118128
const elements = useMemo(() => {
119129
if (!props.inspect) {
120130
return null
@@ -230,7 +240,7 @@ export const ObjectType: React.FC<DataItemProps<object>> = (props) => {
230240
objectSortKeys
231241
])
232242
const marginLeft = props.inspect ? 0.6 : 0
233-
const width = useJsonViewerStore(store => store.indentWidth)
243+
const width = useAtomValue(indentWidthAtom)
234244
const indentWidth = props.inspect ? width - marginLeft : width
235245
const isEmptyValue = useMemo(() => getValueSize(props.value) === 0, [props.value])
236246
if (isEmptyValue) {

src/components/DataTypes/createEasyType.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { InputBase } from '@mui/material'
2+
import { useAtomValue } from 'jotai'
23
import React, { useCallback } from 'react'
34

4-
import { useJsonViewerStore } from '../../stores/JsonViewerStore'
5+
import { colorspaceAtom, displayDataTypesAtom } from '../../state'
56
import type { Colorspace } from '../../theme/base16'
67
import type { DataItemProps, DataType, EditorProps } from '../../type'
78
import { DataTypeLabel } from '../DataTypeLabel'
@@ -19,9 +20,8 @@ export function createEasyType<Value> (
1920
const displayTypeLabel = config.displayTypeLabel ?? true
2021
const Render = React.memo(renderValue)
2122
const EasyType: React.FC<DataItemProps<Value>> = (props) => {
22-
const storeDisplayDataTypes = useJsonViewerStore(store => store.displayDataTypes)
23-
const color = useJsonViewerStore(
24-
store => store.colorspace[config.colorKey])
23+
const storeDisplayDataTypes = useAtomValue(displayDataTypesAtom)
24+
const color = useAtomValue(colorspaceAtom)[config.colorKey]
2525
return (
2626
<DataBox
2727
sx={{
@@ -43,8 +43,7 @@ export function createEasyType<Value> (
4343
}
4444
const fromString = config.fromString
4545
const EasyTypeEditor: React.FC<EditorProps<Value>> = ({ value, setValue }) => {
46-
const color = useJsonViewerStore(
47-
store => store.colorspace[config.colorKey])
46+
const color = useAtomValue(colorspaceAtom)[config.colorKey]
4847
return (
4948
<InputBase
5049
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

78
/**
@@ -22,7 +23,7 @@ export function useClipboard ({ timeout = 2000 } = {}) {
2223
copyTimeout.current = window.setTimeout(() => setCopied(false), timeout)
2324
setCopied(value)
2425
}, [timeout])
25-
const onCopy = useJsonViewerStore(store => store.onCopy)
26+
const onCopy = useAtomValue(onCopyAtom)
2627

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

src/hooks/useInspect.ts

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,48 @@
1-
import {
2-
Dispatch,
3-
SetStateAction,
4-
useCallback,
5-
useEffect,
6-
useState
7-
} from 'react'
1+
import { useAtomValue, useSetAtom } from 'jotai'
2+
import { useAtomCallback } from 'jotai/utils'
3+
import { useCallback, useEffect, useState } from 'react'
84

95
import {
10-
useJsonViewerStore
11-
} from '../stores/JsonViewerStore'
6+
defaultInspectDepthAtom,
7+
getInspectCacheAtomFamily,
8+
setInspectCacheAtomFamily
9+
} from '../state'
1210
import { useIsCycleReference } from './useIsCycleReference'
1311

1412
export function useInspect (path: (string | number)[], value: any, nestedIndex?: number) {
1513
const depth = path.length
1614
const isTrap = useIsCycleReference(path, value)
17-
const getInspectCache = useJsonViewerStore(store => store.getInspectCache)
18-
const setInspectCache = useJsonViewerStore(store => store.setInspectCache)
19-
const defaultInspectDepth = useJsonViewerStore(store => store.defaultInspectDepth)
15+
const defaultInspectDepth = useAtomValue(defaultInspectDepthAtom)
16+
17+
const getInspectCache = useAtomCallback(
18+
useCallback((get, set, arg) => {
19+
// eslint-disable-next-line react-hooks/rules-of-hooks
20+
useAtomValue(getInspectCacheAtomFamily(arg))
21+
}, [])
22+
)
23+
const setInspectCache = useAtomCallback(
24+
useCallback((get, set, arg) => {
25+
// eslint-disable-next-line react-hooks/rules-of-hooks
26+
useSetAtom(setInspectCacheAtomFamily(arg))
27+
}, [])
28+
)
2029
useEffect(() => {
21-
const inspect = getInspectCache(path, nestedIndex)
30+
const inspect = getInspectCache({ path, nestedIndex })
2231
if (inspect !== undefined) {
2332
return
2433
}
2534
if (nestedIndex !== undefined) {
26-
setInspectCache(path, false, nestedIndex)
35+
setInspectCache({ path, action: false, nestedIndex })
2736
} else {
2837
// do not inspect when it is a cycle reference, otherwise there will have a loop
2938
const inspect = isTrap
3039
? false
3140
: depth < defaultInspectDepth
32-
setInspectCache(path, inspect)
41+
setInspectCache({ path, inspect })
3342
}
34-
}, [defaultInspectDepth, depth, getInspectCache, isTrap, nestedIndex, path, setInspectCache])
35-
const [inspect, set] = useState<boolean>(() => {
36-
const shouldInspect = getInspectCache(path, nestedIndex)
43+
}, [defaultInspectDepth, depth, isTrap, nestedIndex, path, getInspectCache, setInspectCache])
44+
const shouldInspect = useAtomValue(getInspectCacheAtomFamily({ path, nestedIndex }))
45+
const [inspect, setOriginal] = useState<boolean>(() => {
3746
if (shouldInspect !== undefined) {
3847
return shouldInspect
3948
}
@@ -44,12 +53,15 @@ export function useInspect (path: (string | number)[], value: any, nestedIndex?:
4453
? false
4554
: depth < defaultInspectDepth
4655
})
47-
const setInspect = useCallback<Dispatch<SetStateAction<boolean>>>((apply) => {
48-
set((oldState) => {
49-
const newState = typeof apply === 'boolean' ? apply : apply(oldState)
50-
setInspectCache(path, newState, nestedIndex)
51-
return newState
52-
})
53-
}, [nestedIndex, path, setInspectCache])
56+
const setInspect = useAtomCallback(
57+
useCallback((get, set, apply) => {
58+
setOriginal((oldState) => {
59+
const newState = typeof apply === 'boolean' ? apply : apply(oldState)
60+
// eslint-disable-next-line react-hooks/rules-of-hooks
61+
useSetAtom(setInspectCacheAtomFamily(apply))
62+
return newState
63+
})
64+
}, [])
65+
)
5466
return [inspect, setInspect] as const
5567
}

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)