Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a React prop type be defined recursively?

Suppose we're defining a React class that will display a tree.

React.createClass({
    propTypes: {
        tree: treeType
    },
    render: function () {
        // ...
    }
});

Here's a definition of treeType that obviously doesn't work but hopefully illustrates what I'm trying to express.

var treeType = React.PropTypes.shape({
    value: React.PropTypes.string,
    children: React.PropTypes.arrayOf(treeType)
})

Is there a way to let the type refer to itself lazily so this can work?

like image 639
Chris Martin Avatar asked Aug 18 '15 03:08

Chris Martin


2 Answers

A React prop type is just a function, so it can be referenced lazily like this:

function lazyFunction(f) {
    return function () {
        return f.apply(this, arguments);
    };
}

var lazyTreeType = lazyFunction(function () { 
    return treeType;
});

var treeType = React.PropTypes.shape({
    value: React.PropTypes.string.isRequired,
    children: React.PropTypes.arrayOf(lazyTreeType)
})

The rest of the code for a complete working example (also available as a jsfiddle):

function hasChildren(tree) {
    return !!(tree.children && tree.children.length);
}

var Tree = React.createClass({
    propTypes: {
        tree: treeType
    },
    render: function () {
        return this.renderForest([this.props.tree], '');
    },
    renderTree: function (tree, key) {
        return <li className="tree" key={key}>
            <div title={key}>{tree.value}</div>
            {hasChildren(tree) &&
                this.renderForest(tree.children, key)}
        </li>;
    },
    renderForest: function (trees, key) {
        return <ol>{trees.map(function (tree) {
            return this.renderTree(tree, key + ' | ' + tree.value);
        }.bind(this))}</ol>;
    }
});

var treeOfLife = { value: "Life", children: [
    {value: "Animal", children: [
        {value: "Dog"},
        {value: "Cat"}
    ]},
    {value: "Plant"}
]};

React.render(
    <Tree tree={treeOfLife}/>,
    document.getElementById('tree'));

Screenshot of the result:

Screenshot of the result

like image 124
Chris Martin Avatar answered Oct 24 '22 07:10

Chris Martin


Here's another approach, courtesy of jethrolarson on GitHub:

Given the recursive component Tree

import React from 'react';

const Tree = ({treeData}) => (
    <div>
        {treeData.nodeName}{' '}
        {treeData.children.map(subTree => (
            <Tree treeData={subTree} />
        ))}
    </div>
);

that takes a tree data structure like the one below

                Root
                /  \
           Child1   Child2
          /     \        \
     GChild1   GChild2   GChild3

(as code:

const treeData = {
    nodeName: "Root",
    children: [
        {
            nodeName: "Child1",
            children: [
                {nodeName: "GChild1"},
                {nodeName: "GChild2"},
            ]
        },
        {
            nodeName: "Child2",
            children: [
                {nodeName: "GChild3"},
            ]
        },
    ]
};

),

the propTypes for Tree can be defined as:

import PropTypes from 'prop-types';

const treeDataShape = {
    nodeName: PropTypes.string.isRequired,
};
treeDataShape.children = PropTypes.arrayOf(PropTypes.shape(treeDataShape));

Tree.propTypes = {
    treeData: PropTypes.shape(treeDataShape),
};

Note how all of the references to treeDataShape refer to the same object. Defining children after the object is created lets you recursively reference the same object.

like image 42
user5670895 Avatar answered Oct 24 '22 06:10

user5670895