Greg Forel
Greg Forel

Reputation: 2569

Inject a React component as a prop when it has mandatory props in Typescript

I'm building a React TreeView component with Typescript. I would like the flexibility to be able to inject a template TreeNode component that renders each node.

How can I type my TreeNode's props so I can inject TreeNode into TreeView as a prop without typescript complaining that I'm not providing my TreeNodes props yet, even though they will be down the road when TreeView builds the hierarchy and feeds each TreeNode with appropriate props.

I tried to strip down my code to the minimum:

interface TreeNodeProps {
    id: string
    title: string
}

const TreeNode: React.SFC<TreeNodeProps > = (props) => {
  return (
    <div key={props.id}>
        <div>{props.title}</div>
        <div>{props.children}</div>
    </div>
  )
}

const TreeView: React.SFC<{treeData: any, TreeNode: JSX.Element}> = (props) => {
  // here's the logic where each TreeNode's props will be fed with graph's props.
    const buildTreeRecursively = (treeData) =>
        treeData.map((node) => {                
            if (node.children.length > 0) {
                return (
                    <TreeNode id={node.id} title={node.title}>
                        {buildTreeRecursively(node.children)}
                    </TreeNode>
                )
            }
            return <TreeNode id={node.id} title={node.title} />
        })

  return (
        <div className={styles.treeView}>
            {buildTreeRecursively(props.treeData)}
        </div>
  )
}

Upvotes: 1

Views: 2250

Answers (1)

Patrick Roberts
Patrick Roberts

Reputation: 51816

This seems to work:

const buildTreeRecursively = (treeData: React.PropsWithChildren<TreeNodeProps>[]) =>
    treeData.map((node) => {
        if (node.children) {
            return (
                <TreeNode id={node.id} title={node.title}>
                    {buildTreeRecursively(node.children as React.PropsWithChildren<TreeNodeProps>[])}
                </TreeNode>
            )
        }
        return <TreeNode id={node.id} title={node.title} />
    })

If I leave the if (node.children.length > 0), I get the compile error

Object is possibly 'null' or 'undefined'.

and since the type of node.children is a superset of React.PropsWithChildren<TreeNodeProps>[], we have to cast it since we know we're only using that subset of the type.

I got the idea for treeData's type by looking at the type hint for props from

const TreeNode: React.SFC<TreeNodeProps> = (props) => {
  return (
    <div key={props.id}>
        <div>{props.title}</div>
        <div>{props.children}</div>
    </div>
  )
}

which was React.PropsWithChildren<TreeNodeProps>.

Upvotes: 1

Related Questions