Skip to content

Remove UNSAFE_componentWillReceiveProps from react-json-tree #1049

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

defunctzombie
Copy link

@defunctzombie defunctzombie commented Jan 23, 2022

Reworking of #644 to remove UNSAFE_componentWillReceiveProps which is deprecated in react 17 and likely removed in 18.

Components that used UNSAFE_componentWillReceiveProps are converted to functional components with hooks.

Update JSONTree to a functional component to remove the unsave receive props. The
functional component uses a useMemo hook on the theme to avoid creating the theme style
unless the theme has changed.
@defunctzombie defunctzombie marked this pull request as draft January 23, 2022 22:37
@defunctzombie defunctzombie force-pushed the roman/react-json-tree-remove-unsafe branch from c6db35f to 68dba97 Compare January 23, 2022 22:40
@@ -47,133 +46,56 @@ const defaultLabelRenderer = ([label]: (string | number)[]) => (
);
const noCustomNode = () => false;

function checkLegacyTheming(theme: Theme | undefined, props: Props) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Methuselah96 As part of this update I removed support for the deprecated styling methods. Removing these is not strictly required to do in this change (just something that made the code easier to reason about). Since this could be seen as a breaking change I am happy to put this back if desired.

@defunctzombie defunctzombie marked this pull request as ready for review January 24, 2022 20:04
@Methuselah96
Copy link
Member

@defunctzombie Looks like there are some type errors:

Error: src/JSONArrayNode.tsx(21,4): error TS2322: Type '{ data: any; nodeType: string; nodeTypeIndicator: string; createItemString: (data: any) => string; expandable: boolean; styling: StylingFunction; keyPath: (string | number)[]; ... 12 more ...; children?: ReactNode; }' is not assignable to type 'Pick<Props, "nodeType" | "styling" | "keyPath" | "labelRenderer" | "valueRenderer" | "circularCache" | "level" | "isCircular" | "shouldExpandNode" | "hideRoot" | "getItemString" | ... 6 more ... | "expandable">'.
  Types of property 'circularCache' are incompatible.
    Type 'any[] | undefined' is not assignable to type 'any[]'.
      Type 'undefined' is not assignable to type 'any[]'.
Error: src/JSONIterableNode.tsx(33,6): error TS2741: Property 'expandable' is missing in type '{ nodeType: string; nodeTypeIndicator: string; createItemString: (data: any, limit: number) => string; data: any; styling: StylingFunction; keyPath: (string | number)[]; ... 12 more ...; children?: ReactNode; }' but required in type 'Pick<Props, "nodeType" | "styling" | "keyPath" | "labelRenderer" | "valueRenderer" | "circularCache" | "level" | "isCircular" | "shouldExpandNode" | "hideRoot" | "getItemString" | ... 6 more ... | "expandable">'.
Error: src/JSONObjectNode.tsx(20,4): error TS2322: Type '{ data: any; nodeType: string; nodeTypeIndicator: string; createItemString: (data: any) => string; expandable: boolean; styling: StylingFunction; keyPath: (string | number)[]; ... 12 more ...; children?: ReactNode; }' is not assignable to type 'Pick<Props, "nodeType" | "styling" | "keyPath" | "labelRenderer" | "valueRenderer" | "circularCache" | "level" | "isCircular" | "shouldExpandNode" | "hideRoot" | "getItemString" | ... 6 more ... | "expandable">'.
  Types of property 'circularCache' are incompatible.
    Type 'any[] | undefined' is not assignable to type 'any[]'.
      Type 'undefined' is not assignable to type 'any[]'.

@changeset-bot
Copy link

changeset-bot bot commented Jan 30, 2022

⚠️ No Changeset found

Latest commit: cf66127

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@defunctzombie
Copy link
Author

@Methuselah96 I looked at the types here and can't make sense of where this error comes from or what the intended behavior is. I did not change any of the Prop types in this PR.

The error comes from a miss-match between these two types:

https://github.com/reduxjs/redux-devtools/blob/main/packages/react-json-tree/src/types.ts#L54
https://github.com/reduxjs/redux-devtools/blob/main/packages/react-json-tree/src/types.ts#L65

One being optional for circularCache.

@Methuselah96
Copy link
Member

Methuselah96 commented Jan 30, 2022

Previously, circularCache was defaulted to an empty array in JSONNestedNode if it wasn't provided, however it looks like this PR does not initialize the circular cache (and perhaps also fails to update it?).

@defunctzombie
Copy link
Author

Even if I updated JSONNestedNode to set a default for circularCache, that doesn't prevent the error. The issue is that JSONArrayNode props are of type CircularPropsPassedThroughJSONNode which extend JSONNestedNodeCircularPropsPassedThroughJSONNode which has circularCache?: any[]; however JSONNestedNode props are CircularPropsPassedThroughJSONNestedNode which extend JSONNestedNodeCircularPropsPassedThroughJSONNestedNode which has circularCache: any[];.

So when JSONArrayNode does <JSONNestedNode {...props} ...etc circularCache types don't match since JSONNestedNode doesn't allow undefined for circularCache. Its possible that the defaultProps from before is treated as turning circularCache: any[] into circularCache?: any[] even tho the Props type does not allow circularCache to be undefined.

The two fixes are:

  • add circularCache={props.circularCache ?? emptyArray} to JSONArrayNode's use of JSONNestedNode
  • update the Props for JSONNestedNode to allow circularCache to be optional

Preferences?

@Methuselah96
Copy link
Member

Methuselah96 commented Jan 31, 2022

Right, if a prop is specified in defaultProps then TypeScript will make that prop optional when the component is used.

Option 1 is not preferable because JSONArrayNode is not the only component that uses JSONNestedNode (JSONIterableNode and JSONObjectNode do as well). Option 2 sounds good to me (accompanied with actually defaulting circularCache to an empty array and updating it appropriately).

@defunctzombie
Copy link
Author

I've updated the typescript prop types to reflect allowing undefined.

@defunctzombie
Copy link
Author

@Methuselah96 anything else that I need to do on this PR before it can be merged?

Copy link
Member

@Methuselah96 Methuselah96 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on! I think there are a few things that still need to be addressed before this can be merged. Also, can you add a changeset that includes a description of the change and notes the breaking change of removing the legacy theming props?

Comment on lines +67 to +79
<JSONNode
postprocessValue={postprocessValue}
hideRoot={hideRoot}
styling={styling}
shouldExpandNode={shouldExpandNode}
getItemString={getItemString}
labelRenderer={labelRenderer}
valueRenderer={valueRenderer}
isCustomNode={isCustomNode}
collectionLimit={collectionLimit}
keyPath={hideRoot ? [] : keyPath}
value={postprocessValue(value)}
/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we failing to pass-through sortObjectKeys here? Speaking of which, can we make it match the pattern of the other props by making sortObjectKeys required in JSONNestedNodeCircularPropsPassedThroughJSONTree, default it to false in this component, and remove | undefined when it's listed in the parameters for getCollectionEntries and getEntries?

Comment on lines 63 to +76
export type CircularPropsPassedThroughJSONNestedNode =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;
JSONNestedNodeCircularPropsPassedThroughJSONNode;

export type CircularPropsPassedThroughRenderChildNodes =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;
JSONNestedNodeCircularPropsPassedThroughJSONNode;

export type CircularPropsPassedThroughItemRange =
SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;
JSONNestedNodeCircularPropsPassedThroughJSONNode;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we go ahead and get rid of these three types since they're now identical to CircularPropsPassedThroughJSONNode and just use that everywhere?

Comment on lines +148 to +152
const expandableLatest = useRef<boolean>(expandable);
expandableLatest.current = expandable;
const handleClick = useCallback(() => {
if (expandableLatest.current) {
setExpanded((prevValue) => !prevValue);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the intention of the useRef is to avoid recreating handleClick and causing unnecessary re-renders if expandable changes? I'd rather avoid this if possible since it's discouraged by the React docs. My intuition is that the expandable prop is not going to change often and when it does, I think it's going to require a re-render anyway so it seems like a premature optimization at the cost of more complicated code.

Comment on lines +124 to +126
setExpanded(() => {
return !isCircular ? shouldExpandNode(keyPath, data, level) : false;
});
Copy link
Member

@Methuselah96 Methuselah96 Feb 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this is passed as a function into setExpanded? This seems like it would be equivalent to just calculating it without a function.

Comment on lines +122 to +127
// When certain props change, we need to re-compute whether our node should be in an expanded state
useEffect(() => {
setExpanded(() => {
return !isCircular ? shouldExpandNode(keyPath, data, level) : false;
});
}, [isCircular, data, keyPath, level, shouldExpandNode]);
Copy link
Member

@Methuselah96 Methuselah96 Feb 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this would reintroduce the undesired behavior discussed here since shouldExpandNode is often intended to only indicate whether a node should be expanded on the first render. This was fixed by evaluating shouldExpandNode for the previous props and the current props and only updating the expanded state if they differ.

We're unfortunately stuck in an awkward situation where this component is partially controlled and it's unclear when shouldExpandNode should be called. We either need to preserve the previous behavior (which seems difficult if we don't want to keep around a reference to the previous props) or come up with a better solution that avoids the difficulties that arise from a prop that allows for partially controlled state. Feel free to pitch some ideas so we can discuss before putting too much working into a single solution. I'll keep thinking about it myself.

@unematiii
Copy link

@Methuselah96 @defunctzombie Is any of you still working on it?

@Methuselah96
Copy link
Member

I am not currently working on it.

@defunctzombie
Copy link
Author

I too am not currently working on this.

@BENcorry
Copy link

BENcorry commented Jan 4, 2023

i wanna to ask which version has fixed this problem

@Methuselah96
Copy link
Member

It has not been resolved yet, but I am working on a fix that I hope will be released within the next week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants