Björn Boxstart Björn Boxstart - 2 months ago 60
React JSX Question

Using instanceof to test for base class of a React component

To support nested navigation menu's, we're using React.cloneElement to add properties to child menu components (the menu components are custom components, based on react-bootstrap). To prevent that we're cloning all elements even though they are not child menu components, but regular content components, I want make the cloning conditional.

All menu components are sub classes of 'MenuBase' (which itself is a sub class of React.Component). In my code, I tried to test whether a child of a react component (reading this.props.children by use of the React.Children utility functions) is an instance of MenuBase.

Simplified code:

interface IMenuBaseProps {
// menu related properties
}

abstract class MenuBase<P extends IMenuBaseProps, S> extends React.Component<P, S> {
// constructor etc.
}

interface IGeneralMenuProps extends IMenuBaseProps {
// additional properties
}

class GeneralMenu extends MenuBase<IGeneralMenuProps, {}> {
public render(): JSX.Element {
// do some magic
}
}


Somewhere in the menu logic I want to do something like the following

React.Children.map(this.props.children, (child: React.ReactElement<any>): React.ReactElement<any> ==> {
if (child instanceof MenuBase) {
// return clone of child with additional properties
} else {
// return child
}
}


However, this test never results in true and as a result a clone is never made.

In the Chrome developer tools I can see that:


  1. child.type = function GeneralMenu(props)

  2. child.type.prototype = MenuBase



typescript child watch

Can somebody help me to clarify the following:


  1. Why is instanceof not working

  2. If I'm not able to use instance of the test for something in the inheritance chain of react components, what are my alternatives (I know I can test for the existence of one of the properties of IMenuBaseProps, but I don't really like that solution).


Answer

The solution of @NitzanTomer did not seem to work under all circumstances. I was not able to find the cause of the difference in test results on his machine and my machine.

Finally I found the following solution:

public render(): JSX.Element {
    // Iterate over children and test for inheritance from ComponentBase
    let results: JSX.Element[] = React.Children.map(this.props.children, (child: React.ReactElement<any>, index: number): JSX.Element => {
        let isComponent: boolean = typeof child.type !== 'string' && React.Component.prototype.isPrototypeOf((child.type as any).prototype);
        let result: boolean = isComponent ? (child.type as any).prototype instanceof ComponentBase : false; // Not a component, then never a ComponentBase
        return <li>
                <div>Child nr. {index} is a React component: {isComponent ? "True" : "False"}</div>
                <div>And is a sub class of ComponentBase: {result ? "True" : "False"} { result !== this.props.expectedTestResult ? "(unexpected)" : ""}</div>
            </li>;
    })

    return (
        <ul>
            {results}
        </ul>
    )
}

In this solution, first a test is executed to check if the child is a React component. After that, 'instanceof' is used to determine whether the child component is a sub class of 'ComponentBase' (direct or indirect).

Because in TypeScript the 'type' property of React.Component does not have a 'prototype' property a cast to 'any' is necessary.

Comments