implement solution using useRef

This commit is contained in:
lucataglia 2023-05-22 12:29:08 +02:00
parent 0680165deb
commit 4574eb0c62
6 changed files with 56 additions and 45 deletions

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import ItemRange from './ItemRange';
import JSONArrow from './JSONArrow';
import JSONNode from './JSONNode';
@ -113,47 +113,52 @@ export default function JSONNestedNode(props: Props) {
shouldExpandNodeInitially,
styling,
} = props;
const { shouldExpandNode, setShouldExpandNode, setEnableDefaultButton } =
const { shouldExpandNode, setEnableDefaultButton, setShouldExpandNode } =
useExpandableButtonContext();
const [expanded, setExpanded] = useState<boolean>(
const [defaultExpanded] = useState<boolean>(
// calculate individual node expansion if necessary
isCircular ? false : shouldExpandNodeInitially(keyPath, data, level)
isCircular
? false
: (function getDefault(){
switch (shouldExpandNode) {
case 'expand': return true;
case 'collapse': return false;
default: return shouldExpandNodeInitially(keyPath, data, level);
}
})()
);
const defaultExpanded = shouldExpandNodeInitially(keyPath, data, level);
const [, setTriggerReRender] = useState<boolean>(defaultExpanded);
useEffect(() => {
switch (shouldExpandNode) {
case 'expand':
setExpanded(true);
break;
case 'collapse':
setExpanded(false);
break;
case 'default':
setExpanded(defaultExpanded);
break;
default: // Do nothing
}
}, [defaultExpanded, shouldExpandNode]);
/**
* Used the useRef to handle expanded because calling a setState in a recursive implementation
* could lead to a "Maximum update depth exceeded" error */
const expandedRef = useRef<boolean>(defaultExpanded)
switch (shouldExpandNode) {
case 'expand': expandedRef.current = isCircular ? false : true; break;
case 'collapse': expandedRef.current = false; break;
case 'default': expandedRef.current = defaultExpanded; break;
default: //Do nothing;
}
const handleClick = useCallback(() => {
if (expandable) {
setExpanded(!expanded);
expandedRef.current = !expandedRef.current
setTriggerReRender((e) => !e)
setEnableDefaultButton(true);
setShouldExpandNode(undefined);
setShouldExpandNode(undefined)
}
}, [expandable, expanded, setEnableDefaultButton, setShouldExpandNode]);
}, [expandable, setEnableDefaultButton, setShouldExpandNode]);
const renderedChildren =
expanded || (hideRoot && level === 0)
expandedRef.current || (hideRoot && level === 0)
? renderChildNodes({ ...props, circularCache, level: level + 1 })
: null;
const itemType = (
<span {...styling('nestedNodeItemType', expanded)}>
<span {...styling('nestedNodeItemType', expandedRef.current)}>
{nodeTypeIndicator}
</span>
);
@ -164,7 +169,7 @@ export default function JSONNestedNode(props: Props) {
createItemString(data, collectionLimit),
keyPath
);
const stylingArgs = [keyPath, nodeType, expanded, expandable] as const;
const stylingArgs = [keyPath, nodeType, expandedRef.current, expandable] as const;
return hideRoot ? (
<li {...styling('rootNode', ...stylingArgs)}>
@ -178,7 +183,7 @@ export default function JSONNestedNode(props: Props) {
<JSONArrow
styling={styling}
nodeType={nodeType}
expanded={expanded}
expanded={expandedRef.current}
onClick={handleClick}
/>
)}

View File

@ -59,6 +59,19 @@ const getDefaultThemeStyling = (theme: Base16Theme): StylingConfig => {
backgroundColor: colors.BACKGROUND_COLOR,
},
expandable: {
color: colors.TEXT_COLOR,
backgroundColor: colors.BACKGROUND_COLOR,
position: 'absolute',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
gap: '1rem',
top: '1rem',
right: '1rem',
cursor: 'pointer',
},
value: ({ style }, nodeType, keyPath) => ({
style: {
...style,

View File

@ -7,9 +7,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { ReactNode } from 'react';
import { Expandable } from '.';
import { useExpandableButtonContext } from './expandableButtonsContext';
import { StylingFunction } from 'react-base16-styling';
interface ExpandableButtonsProps {
expandable: Expandable;
styling: StylingFunction;
}
interface ExpandButtonProps {
@ -26,24 +28,13 @@ interface DefaultButtonProps {
defaultIcon?: ReactNode;
}
function ExpandableButtons({ expandable }: ExpandableButtonsProps) {
function ExpandableButtons({ expandable, styling }: ExpandableButtonsProps) {
const { enableDefaultButton } = useExpandableButtonContext();
const expandableDefaultValue = expandable?.defaultValue || 'expand';
return (
<div
style={{
position: 'absolute',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
gap: '1rem',
top: '1rem',
right: '1rem',
cursor: 'pointer',
}}
>
<div {...styling('expandable')}>
{enableDefaultButton && (
<DefaultButton defaultIcon={expandable?.defaultIcon} />
)}

View File

@ -7,6 +7,7 @@ import React, {
} from 'react';
import { Expandable } from '.';
import ExpandableButtons from './expandableButtons';
import { StylingFunction } from 'react-base16-styling';
interface Context {
enableDefaultButton: boolean;
@ -18,11 +19,12 @@ interface Context {
interface Props {
children: ReactNode;
expandable?: Expandable;
styling: StylingFunction;
}
const ExpandableButtonsContext = createContext<Context>({} as Context);
function ExpandableButtonsContextProvider({ expandable, children }: Props) {
function ExpandableButtonsContextProvider({ expandable, children, styling }: Props) {
const [enableDefaultButton, setEnableDefaultButton] = useState(false);
const [shouldExpandNode, setShouldExpandNode] = useState();
@ -39,7 +41,7 @@ function ExpandableButtonsContextProvider({ expandable, children }: Props) {
return (
<ExpandableButtonsContext.Provider value={value}>
{children}
{expandable && <ExpandableButtons expandable={expandable} />}
{expandable && <ExpandableButtons expandable={expandable} styling={styling} />}
</ExpandableButtonsContext.Provider>
);
}

View File

@ -67,7 +67,7 @@ export function JSONTree({
return (
<ul {...styling('tree')}>
<ExpandableButtonsContext expandable={expandable}>
<ExpandableButtonsContext expandable={expandable} styling={styling}>
<JSONNode
keyPath={hideRoot ? [] : keyPath}
value={postprocessValue(value)}

View File

@ -2,7 +2,7 @@ import React from 'react';
import { createRenderer } from 'react-test-renderer/shallow';
import { JSONTree } from '../src/index';
import JSONNode from '../src/JSONNode';
import ExpandableButtonsContextProvider from '../src/expandableButtonsContext';
const BASIC_DATA = { a: 1, b: 'c' };
@ -17,6 +17,6 @@ describe('JSONTree', () => {
const result = render(<JSONTree data={BASIC_DATA} />);
expect(result.type).toBe('ul');
expect(result.props.children[0].type.name).toBe(JSONNode.name);
expect(result.props.children.type.name).toBe(ExpandableButtonsContextProvider.name);
});
});