14

I have a generic component which maps its child components to filter only children of a certain type, as found below.

However, using the property type was a mere guess, and I can't find it documented. Not only that, logging it shows it being a function - which can't be executed. On top of that there's a couple issues that need to be worked around when using Browserify.

Another option would be to read child.prototype.displayName. But that too feels wrong.

Question: Basically, I'm looking for a solid way of comparing whether two ReactJS components are equal.

EXAMPLE

(Updated: not that bad after all)

var Foo = React.createClass({
    render: function() {
        return <div>Foo</div>;
    }
});

var Bar = React.createClass({
    render: function() {
        return <div>Bar</div>;
    }
});

var Main = React.createClass({
    render: function() {
        var filteredChildren = [];

        filteredChildren = React.Children.map(function(child) {
            if (child.type === Foo.type) {
                return child;
            }
        });

        return (
            <div>
                {filteredChildren}
            </div>
        );
    }
});

React.render(<Main><Foo /><Bar /></Main>, document.body);
David
  • 173
  • 1
  • 1
  • 7
  • In what sense do you want to know the components are equal? Does equal mean they render the same output? – Brett DeWoody Jan 07 '15 at 17:14
  • 1
    Nope, they may be (and are) totally different. I need to know if they are an instance of the same React.createClass(). – David Jan 07 '15 at 18:05
  • 1
    Related: https://www.bountysource.com/issues/3127455-proptypes-define-children-component-type – David Jan 09 '15 at 14:45

2 Answers2

21

I think your example is correct.

Indeed, in React 0.12 child.type === Foo.type is the only comparison that works.
This is related to React 0.12 being in process of deprecating wrapper functions.

When 0.13 is out, child.type itself will be Foo.

Nitpick: don't use this.props.children.map, this won't work when there is less than two children.
Use React.Children.map instead.

Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • Thanks for shedding some light! This explains why `child.type` appears to be a function. As for the nitpicking, you're totally right, and indeed one should use React.Children.map! – David Jan 08 '15 at 23:45
  • 2
    Although not specifically pertaining to this questions example, if you create ES6 classes rather than using React.createClass, you can use instance of: `child.type.prototype instanceof Foo` -- I discovered that in [my own similar question](http://stackoverflow.com/questions/35584279/looping-this-props-children-how-do-i-test-their-type?stw=2) – mejdev Feb 23 '16 at 18:12
3

The kind of api you're making is frail and confusing. You shouldn't treat elements as data. If you need to filter, pass data to the component.

<Main things={[
  {type: 'Foo', element: <Foo />},
  {type: 'Bar', element: <Bar />},
  {type: 'Bar', element: <div>I'm lying but it doesn't matter</div>},
]} />
var Main = React.createClass({
    render: function(){
        var filteredChildren = this.props.things.map(function(thing){
            return thing.type === 'Foo' ? thing.element : false;
        });

        return <div>{filteredChildren}</div>;
    }
});
Brigand
  • 84,529
  • 20
  • 165
  • 173
  • 3
    Most of the time this is true. There are rare cases when comparing is legit. For example, if your component also exports subcomponents and wants to apply special treatment for specific types, e.g. `some`. – Dan Abramov Jan 08 '15 at 13:46
  • 1
    And why would you need to know which is which in a case like that? Menu.Item would render a menu item, and Menu.Separator would render a separator. And Menu would just render its stuff, and (conditionally) include this.props.children. – Brigand Jan 08 '15 at 14:34
  • I had a use case where I wanted to wrap each of passed `children` in a `div`, except separators. Although you're right, with `Menu.Item` I wouldn't really need this check—I was too lazy to wrap each item. – Dan Abramov Jan 08 '15 at 17:22
  • The case Dan describes is indeed very similar to mine. In my case it's a form component with various child components (input, select, etc.). For form validation purposes all children are given additional props before being rendered, but I hit a point where one input type needed additional props. – David Jan 08 '15 at 23:59
  • To add: I did not want to add an extra prop to all child components just to be able to filter them. I figured there had to be a more abstract way! – David Jan 09 '15 at 00:06
  • Again, data not components. Validate the data, and pass a valid prop to the inputs and the form. Look at something like joi which lets you create validation rules, and then use them on a single value or an object (e.g. this.state). That also allows you to share validation code with the server if you're using node. – Brigand Jan 09 '15 at 02:46