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

View File

@ -58,6 +58,19 @@ const getDefaultThemeStyling = (theme: Base16Theme): StylingConfig => {
WebkitUserSelect: 'none', WebkitUserSelect: 'none',
backgroundColor: colors.BACKGROUND_COLOR, 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) => ({ value: ({ style }, nodeType, keyPath) => ({
style: { style: {

View File

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

View File

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

View File

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

View File

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