Skip to content

Commit 661151a

Browse files
authored
fix: state on nested array (#28)
1 parent 24f9167 commit 661151a

File tree

7 files changed

+84
-62
lines changed

7 files changed

+84
-62
lines changed

examples/basic/pages/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@ const loopArray = [
3535

3636
loopArray[1] = loopArray
3737

38+
const longArray = Array.from({ length: 1000 }).map((_, i) => i)
39+
3840
const example = {
3941
loopObject,
4042
loopArray,
43+
longArray,
4144
string: 'this is a string',
4245
integer: 42,
4346
array: [19, 19, 810, 'test', NaN],

src/components/DataKeyPair.tsx

+30-28
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,29 @@ import { DataBox } from './mui/DataBox'
1818

1919
export type DataKeyPairProps = {
2020
value: unknown
21-
nested?: boolean
21+
nestedIndex?: number
2222
path: (string | number)[]
2323
}
2424

25-
const IconBox = styled(props => <Box {...props} component='span' />)`
25+
const IconBox = styled(props => <Box {...props} component='span'/>)`
2626
cursor: pointer;
2727
padding-left: 0.7rem;
2828
` as typeof Box
2929

3030
export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
31-
const { value, path } = props
31+
const { value, path, nestedIndex } = props
3232
const [tempValue, setTempValue] = useState(value)
3333
const depth = path.length
3434
const key = path[depth - 1]
3535
const hoverPath = useJsonViewerStore(store => store.hoverPath)
3636
const isHover = useMemo(() => {
37-
return hoverPath && path.every((value, index) => value === hoverPath[index])
38-
}, [hoverPath, path])
37+
return hoverPath && path.every(
38+
(value, index) => value === hoverPath.path[index] && nestedIndex ===
39+
hoverPath.nestedIndex)
40+
}, [hoverPath, path, nestedIndex])
3941
const setHover = useJsonViewerStore(store => store.setHover)
4042
const root = useJsonViewerStore(store => store.value)
41-
const [inspect, setInspect] = useInspect(path, value)
43+
const [inspect, setInspect] = useInspect(path, value, nestedIndex)
4244
const [editing, setEditing] = useState(false)
4345
const onChange = useJsonViewerStore(store => store.onChange)
4446
const keyColor = useTextColor()
@@ -106,18 +108,18 @@ export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
106108
{
107109
copied
108110
? (
109-
<CheckIcon
110-
sx={{
111-
fontSize: '.8rem'
112-
}}
113-
/>
111+
<CheckIcon
112+
sx={{
113+
fontSize: '.8rem'
114+
}}
115+
/>
114116
)
115117
: (
116-
<ContentCopyIcon
117-
sx={{
118-
fontSize: '.8rem'
119-
}}
120-
/>
118+
<ContentCopyIcon
119+
sx={{
120+
fontSize: '.8rem'
121+
}}
122+
/>
121123
)
122124
}
123125
</IconBox>
@@ -146,9 +148,9 @@ export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
146148
const KeyRenderer = useJsonViewerStore(store => store.keyRenderer)
147149
return (
148150
<Box className='data-key-pair'
149-
onMouseEnter={
150-
useCallback(() => setHover(path), [setHover, path])
151-
}
151+
onMouseEnter={
152+
useCallback(() => setHover(path, nestedIndex), [setHover, path, nestedIndex])
153+
}
152154
>
153155
<DataBox
154156
component='span'
@@ -171,15 +173,15 @@ export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
171173
{
172174
KeyRenderer.when(downstreamProps)
173175
? <KeyRenderer {...downstreamProps} />
174-
: !props.nested && (
175-
isNumberKey
176-
? <Box component='span'
177-
style={{ color: numberKeyColor }}>{displayKey}</Box>
178-
: <>&quot;{displayKey}&quot;</>
179-
)
176+
: nestedIndex === undefined && (
177+
isNumberKey
178+
? <Box component='span'
179+
style={{ color: numberKeyColor }}>{displayKey}</Box>
180+
: <>&quot;{displayKey}&quot;</>
181+
)
180182
}
181183
{
182-
!props.nested && (
184+
nestedIndex === undefined && (
183185
<DataBox sx={{ mx: 0.5 }}>:</DataBox>
184186
)
185187
}
@@ -188,11 +190,11 @@ export const DataKeyPair: React.FC<DataKeyPairProps> = (props) => {
188190
</DataBox>
189191
{
190192
editing
191-
? (Editor && <Editor value={tempValue} setValue={setTempValue} />)
193+
? (Editor && <Editor value={tempValue} setValue={setTempValue}/>)
192194
: (Component)
193195
? <Component {...downstreamProps} />
194196
: <Box component='span'
195-
className='data-value-fallback'>{`fallback: ${value}`}</Box>
197+
className='data-value-fallback'>{`fallback: ${value}`}</Box>
196198
}
197199
{PostComponent && <PostComponent {...downstreamProps} />}
198200
{(isHover && expandable && !inspect) && actionIcons}

src/components/DataTypes/Object.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export const ObjectType: React.FC<DataItemProps<object>> = (props) => {
111111
return value.map((list, index) => {
112112
const path = [...props.path]
113113
return (
114-
<DataKeyPair key={index} path={path} value={list} nested/>
114+
<DataKeyPair key={index} path={path} value={list} nestedIndex={index}/>
115115
)
116116
})
117117
} else {

src/hooks/useInspect.ts

+18-18
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,33 @@ import {
1111
} from '../stores/JsonViewerStore'
1212
import { useIsCycleReference } from './useIsCycleReference'
1313

14-
export function useInspect (path: (string | number)[], value: any) {
14+
export function useInspect (path: (string | number)[], value: any, nestedIndex?: number) {
1515
const depth = path.length
1616
const isTrap = useIsCycleReference(path, value)
1717
const getInspectCache = useJsonViewerStore(store => store.getInspectCache)
1818
const setInspectCache = useJsonViewerStore(store => store.setInspectCache)
1919
const defaultInspectDepth = useJsonViewerStore(
2020
store => store.defaultInspectDepth)
2121
useEffect(() => {
22-
const inspect = getInspectCache(path)
22+
const inspect = getInspectCache(path, nestedIndex)
2323
if (inspect === undefined) {
24-
// do not inspect when it is a cycle reference, otherwise there will have a loop
25-
const inspect = isTrap
26-
? false
27-
: depth < defaultInspectDepth
28-
setInspectCache(path, inspect)
24+
if (nestedIndex !== undefined) {
25+
setInspectCache(path, false, nestedIndex)
26+
} else {
27+
// do not inspect when it is a cycle reference, otherwise there will have a loop
28+
const inspect = isTrap
29+
? false
30+
: depth < defaultInspectDepth
31+
setInspectCache(path, inspect)
32+
}
2933
}
30-
}, [
31-
defaultInspectDepth,
32-
depth,
33-
getInspectCache,
34-
isTrap,
35-
path,
36-
setInspectCache
37-
])
34+
}, [defaultInspectDepth, depth, getInspectCache, isTrap, nestedIndex, path, setInspectCache])
3835
const [inspect, set] = useState<boolean>(() => {
39-
const shouldInspect = getInspectCache(path)
36+
const shouldInspect = getInspectCache(path, nestedIndex)
4037
if (shouldInspect === undefined) {
38+
if (nestedIndex !== undefined) {
39+
return false
40+
}
4141
return isTrap
4242
? false
4343
: depth < defaultInspectDepth
@@ -47,9 +47,9 @@ export function useInspect (path: (string | number)[], value: any) {
4747
const setInspect = useCallback<Dispatch<SetStateAction<boolean>>>((apply) => {
4848
set((oldState) => {
4949
const newState = typeof apply === 'boolean' ? apply : apply(oldState)
50-
setInspectCache(path, newState)
50+
setInspectCache(path, newState, nestedIndex)
5151
return newState
5252
})
53-
}, [path, setInspectCache])
53+
}, [nestedIndex, path, setInspectCache])
5454
return [inspect, setInspect] as const
5555
}

src/stores/JsonViewerStore.ts

+27-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import create from 'zustand'
33
import createContext from 'zustand/context'
44
import { combine } from 'zustand/middleware'
55

6-
import type { JsonViewerOnChange } from '..'
6+
import type { JsonViewerOnChange, Path } from '..'
77
import type { ColorNamespace } from '../theme/base16'
88
import { lightColorNamespace } from '../theme/base16'
99
import type { JsonViewerKeyRenderer } from '../type'
@@ -13,7 +13,7 @@ DefaultKeyRenderer.when = () => false
1313

1414
export type JsonViewerState = {
1515
inspectCache: Record<string, boolean>
16-
hoverPath: (string | number)[] | null
16+
hoverPath: { path: Path; nestedIndex?: number } | null
1717
groupArraysAfterLength: number
1818
defaultInspectDepth: number
1919
colorNamespace: ColorNamespace
@@ -25,9 +25,10 @@ export type JsonViewerState = {
2525
}
2626

2727
export type JsonViewerActions = {
28-
getInspectCache: (path: (string | number)[]) => boolean
29-
setInspectCache: (path: (string | number)[], action: SetStateAction<boolean>) => void
30-
setHover: (path: (string | number)[] | null) => void
28+
getInspectCache: (path: Path, nestedIndex?: number) => boolean
29+
setInspectCache: (
30+
path: Path, action: SetStateAction<boolean>, nestedIndex?: number) => void
31+
setHover: (path: Path | null, nestedIndex?: number) => void
3132
}
3233

3334
export const createJsonViewerStore = () =>
@@ -46,21 +47,36 @@ export const createJsonViewerStore = () =>
4647
keyRenderer: DefaultKeyRenderer
4748
},
4849
(set, get) => ({
49-
getInspectCache: (path) => {
50-
return get().inspectCache[path.join('.')]
50+
getInspectCache: (path, nestedIndex) => {
51+
const target = nestedIndex !== undefined
52+
? path.join('.') +
53+
`[${nestedIndex}]nt`
54+
: path.join('.')
55+
return get().inspectCache[target]
5156
},
52-
setInspectCache: (path, action) => {
53-
const target = path.join('.')
57+
setInspectCache: (path, action, nestedIndex) => {
58+
const target = nestedIndex !== undefined
59+
? path.join('.') +
60+
`[${nestedIndex}]nt`
61+
: path.join('.')
5462
set(state => ({
5563
inspectCache: {
5664
...state.inspectCache,
57-
[target]: typeof action === 'function' ? action(state.inspectCache[target]) : action
65+
[target]: typeof action === 'function'
66+
? action(
67+
state.inspectCache[target])
68+
: action
5869
}
5970
}))
6071
},
61-
setHover: (path) => {
72+
setHover: (path, nestedIndex) => {
6273
set({
6374
hoverPath: path
75+
? ({
76+
path,
77+
nestedIndex
78+
})
79+
: null
6480
})
6581
}
6682
})

src/stores/typeRegistry.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ export function matchTypeComponents<Value> (value: Value): DataType<Value> {
2121
for (const T of typeRegistry) {
2222
if (T.is(value)) {
2323
potential = T
24-
}
25-
if (typeof value === 'object' && value === null) {
26-
return T
24+
if (typeof value === 'object') {
25+
// early return for case like `null`
26+
return T
27+
}
2728
}
2829
}
2930
if (potential === undefined) {

src/type.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Dispatch, SetStateAction } from 'react'
22
import type React from 'react'
33

4-
type Path = (string | number)[]
4+
export type Path = (string | number)[]
55

66
export interface DataItemProps<ValueType = unknown> {
77
inspect: boolean

0 commit comments

Comments
 (0)