From 4574eb0c6261c3f67659ced1ee3b70913234cff4 Mon Sep 17 00:00:00 2001 From: lucataglia Date: Mon, 22 May 2023 12:29:08 +0200 Subject: [PATCH] implement solution using useRef --- .../react-json-tree/src/JSONNestedNode.tsx | 59 ++++++++++--------- .../src/createStylingFromTheme.ts | 13 ++++ .../react-json-tree/src/expandableButtons.tsx | 17 ++---- .../src/expandableButtonsContext.tsx | 6 +- packages/react-json-tree/src/index.tsx | 2 +- packages/react-json-tree/test/index.spec.tsx | 4 +- 6 files changed, 56 insertions(+), 45 deletions(-) diff --git a/packages/react-json-tree/src/JSONNestedNode.tsx b/packages/react-json-tree/src/JSONNestedNode.tsx index c05c3878..f4492484 100644 --- a/packages/react-json-tree/src/JSONNestedNode.tsx +++ b/packages/react-json-tree/src/JSONNestedNode.tsx @@ -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( + const [defaultExpanded] = useState( // 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(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(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 = ( - + {nodeTypeIndicator} ); @@ -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 ? (
  • @@ -178,7 +183,7 @@ export default function JSONNestedNode(props: Props) { )} diff --git a/packages/react-json-tree/src/createStylingFromTheme.ts b/packages/react-json-tree/src/createStylingFromTheme.ts index 0500f238..189f5651 100644 --- a/packages/react-json-tree/src/createStylingFromTheme.ts +++ b/packages/react-json-tree/src/createStylingFromTheme.ts @@ -58,6 +58,19 @@ const getDefaultThemeStyling = (theme: Base16Theme): StylingConfig => { WebkitUserSelect: 'none', 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: { diff --git a/packages/react-json-tree/src/expandableButtons.tsx b/packages/react-json-tree/src/expandableButtons.tsx index cbf909bc..8aa66b25 100644 --- a/packages/react-json-tree/src/expandableButtons.tsx +++ b/packages/react-json-tree/src/expandableButtons.tsx @@ -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 ( -
    +
    {enableDefaultButton && ( )} diff --git a/packages/react-json-tree/src/expandableButtonsContext.tsx b/packages/react-json-tree/src/expandableButtonsContext.tsx index b57ca79f..b1a2f82c 100644 --- a/packages/react-json-tree/src/expandableButtonsContext.tsx +++ b/packages/react-json-tree/src/expandableButtonsContext.tsx @@ -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({} 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 ( {children} - {expandable && } + {expandable && } ); } diff --git a/packages/react-json-tree/src/index.tsx b/packages/react-json-tree/src/index.tsx index 5e454db1..8ed3a099 100644 --- a/packages/react-json-tree/src/index.tsx +++ b/packages/react-json-tree/src/index.tsx @@ -67,7 +67,7 @@ export function JSONTree({ return (
      - + { const result = render(); expect(result.type).toBe('ul'); - expect(result.props.children[0].type.name).toBe(JSONNode.name); + expect(result.props.children.type.name).toBe(ExpandableButtonsContextProvider.name); }); });