React: Can we have React.Children.filter?

Created on 27 Jan 2015  ·  34Comments  ·  Source: facebook/react

My use case: wrapping all non-empty children into divs.

  render() {
    return (
      <div style={rootStyle} onWheel={this.handleWheel}>
        {Children.map(this.props.children, this.wrapChild)}
      </div>
    );
  },

  wrapChild(child, index) {
    if (!child) { // I know this is probably wrong kind of comparison; don't care
      return (
        <div style={this.getChildStyle(index)}>
          {child}
        </div>
      );
    }
  },

This is all nice but I also need to know how many valid children I have wrapped.
Children.count will return the number of children including the “empty” children. I want to strip them out.

Can we have Children.filter?

Feature Request

Most helpful comment

Hi!

I've made a quick small library with more functions for Children structure here

https://github.com/fernandopasik/react-children-utilities/blob/master/src/index.js

There are also recursive ways to do find, map and more stuff. Check them out, and let me know if there are more I can add.

Thanks!

All 34 comments

Another way to solve this is to give us Children.toArray.

Solved my problem with react/lib/flattenChildren. Wish it were exposed/supported though.

I may be wrong, but I think this is generally problematic unless React.Children.filter would replace filtered children with null or undefined rather than compact the array (which you can/should do with map). If the filter is in any way dynamic and the children aren't keyed then the reconciliation will suffer badly.

Right, I see. What about exposing flattenChildren?

flattenChildren returns a keyed object, this is deprecated (keyed objects) and will not be supported soon AFAIK (Map will be, but then you'd have to polyfill or wait for ES6).

Also see #2932 which would solve this.

So the proper way of counting non-empty children is doing a Children.forEach and incrementing a counter?

If you do need to implement extra functions like this you can bend map to implement other functions due to how JS works. Here's an example in jsbin/gist.

var cutils = utilsFromMap(React.Children.map);
var countNonEmpty = function(children){
    return cutils.reduce(function(acc, x){
        // assumes non-empty means 'truthy'
        return x ? acc + 1 : acc;
    }, 0, children);
}

You can implement your own Children.toArray:

var children = [];
React.Children.forEach(this.props.children, function(child){
    if (!child){
        children.push(child);
    }
});

syranide wrote:
_I may be wrong, but I think this is generally problematic unless React.Children.filter would replace filtered children with null or undefined rather than compact the array (which you can/should do with map). If the filter is in any way dynamic and the children aren't keyed then the reconciliation will suffer badly._

Maybe I'm missing something, but I don't see the problem exactly. Could you elaborate?

FYI: I want to implement a Tabs component, and I thought it would be nice idea to define the individual tabs using nested Tab components. But besides tabs, the tabs header can also contain buttons. So I'd get a structure like this:

<Tabs>
    <Tab key='tab1' label='First tab'>/* tab content */</Tab>
    <Tab key='tab2' label='Second tab'>/* tab content */</Tab>
    <TabButton /* props */ />
</Tabs>

In the Tabs component itself I would now like to know which children are tabs and which are buttons. So it'd be great it I could do this:

var tabs = React.Children.filter(this.props.children, function(child) { /* test if it is a tab */ });

@arendjr I'd call that bad practice, Tab and TabButton are unrelated and should be provided through separate props, not as a mixed soup of children.

@syranide I'm not sure I agree that this is bad practice. It seems perfectly reasonable to me that a parent component might want to have arbitrary behaviors based on their children. In particular, if components want to preserve the absolute ordering of the child components, there isn't really any other way to do it (do the tabs come before/after the tab buttons? or do they alternate? or are they otherwise mixed?). I agree that's not what he is doing here (he doesn't appear to be preserving order), but I think it's a completely valid use case. You could imagine someone implementing a "smart div" that does fancy layout things, and treats some components differently from others (obviously it's natural to have a mixed bag of children).

Given the simplicity of @ofersadgat's implementation, I'm not convinced it makes sense to pollute the API space by providing a filter function.

@syranide I agree reconciliation would suffer. Maybe a React.Children.filter would warn if children do not have keys? That is almost a reason to provide this function, to add a warn hook.

It's probably a separate issue, but as @gaearon suggested, it seems like there should be a natural way to get children as a (potentially empty) array. Children.toArray might be a nice solution to this issue.

cc @spicyj

I'm not sure I agree that this is bad practice. It seems perfectly reasonable to me that a parent component might want to have arbitrary behaviors based on their children

@jsfb A very broad subject it seems to me, processing children is not inherently bad, I think it depends on what you're trying to accomplish. If children need to be separated out it's _likely_ that they should be separate props to begin with or the should be API redesigned somehow,

You could imagine someone implementing a "smart div" that does fancy layout things, and treats some components differently from others (obviously it's natural to have a mixed bag of children).

Sure I can imagine that, but I'm not quite sure when that actually makes practical sense and if there aren't better solutions to the problem. Inventing non-standard behaviors on a per-component basis seems like it should be a last resort, to be used only after you've concluded that established practices isn't a good fit. It shouldn't be all-or-nothing, just because children can be used and looks pretty, doesn't mean they should.

The solution could imaginably be <Tabs tabs={[...]} />, it's no longer an opaque structure and the special behavior likely apparent to the user as well (just saying). If you ask me, each tool should be used only for its intended purpose, children has a purpose too and I think it's a mistake to expand that definition unless we're sure it makes sense.

I agree that it depends on what you're trying to accomplish and that it's likely they should be separated out if you're going to try to filter them anyway. But if someone desires a heterogeneous list of components (usually due to ordering concerns), I think that's a perfectly valid use case, and 'children' may be a natural place for those to be defined. We should be providing reasonable support for that use case.

At Facebook, it's common to branch based on the type of a child. Popularity at Facebook doesn't necessarily imply that it's a best practice, but it is a common use case that we do support. I don't know what percentage of those use cases are doing a filter operation (I imagine it's not a large percentage, because presumably the reason to use a heterogeneous list is to maintain order).

Having said that, I'm still not entirely convinced this feature (React.Children.filter) makes sense / justifies the API creep. The only real advantage I see is that it gives us an opportunity to warn about unkey'd children.

It's true that Facebook has existing components that branch based on the type (or position) of a child, but it's usually a better idea to pass separate components explicitly. Instead of

<LeftRight>
  {left}
  {right}
</LeftRight>

you can do

<LeftRight left={left} right={right} />

which is more explicit in addition to being easier to implement. Going back to the original issue, we were considering implementing some sort of React.Children.toArray which clones children and re-keys them appropriately. For example

React.Children.toArray([
  [<div key="monkey" />, <span key="gorilla" />],
  <input key="gorilla" />
])

would return something like

[
  <div key="0.$monkey" />,
  <span key="0.$gorilla" />,
  <input key="$gorilla" />,
]

which is more or less how React flattens children internally. At that point you could manipulate the array in whatever way is most convenient.

React.Children.toArray() sounds good!

const {children} = this.props;
children.slice(-1).concat(children, children[0]).map(
 (item, index) => <li key={index}>{item}</li>
) 

BTW, how to do children concat like this in proper way?

We have React.Children.toArray now.

@kidwm Not sure I understand what you're trying to do there.

@spicyj I don't know how to solve the same key problem after the use React.Children.toArray to concat, so I keep it in this way and assign key by index.

I don't know what key problem you're trying to solve. If you explain it maybe I can help.

@spicyj Thank you very much!

I've tried to do in this way:

const {slides} = React.Children.toArray(this.props.children);
React.Children.map(slides.slice(-1).concat(slides, slides[0]),
 (item) => <li>{item}</li>
)

but didn't know how to address this problem: Warning: flattenChildren(...): Encountered two children with the same key

Something like this would work:

const slides = React.Children.toArray(this.props.children);
const slidesA = slides.map((el) => <li key={'A-' + el.key}>{el}</li>);
const slidesB = slides.map((el) => <li key={'B-' + el.key}>{el}</li>);
const wrappedSlides = slidesA.slice(-1).concat(slidesB, slidesA.slice(0, 1));

though exactly which keys you should assign to which elements depends on how you want to preserve each child's state/identity.

@spicyj if it was possible to alter the key with React.cloneElement, maybe that would be simpler?

const cloneChildren = React.Children.map(this.props.children, (child, index) => React.cloneElement(child, {key: index}));

Yes, that also works. But you rarely want the index as key.

@spicyj Thanks again, this is my final code:

const slides = React.Children.map(this.props.children, (child) => React.cloneElement(child, {key: child.key + '_clone'}));
React.Children.map(slides.slice(-1).concat(this.props.children, slides.slice(0, 1)),
 (item) => <li>{item}</li>
)

I'm doing this by wrapping the Children utility:

import { Children } from 'react';

Children.filter = function (children, filterFn) {
  return Children
    .toArray(children)
    .filter(filterFn);
};

export default Children;

And using it like this so it's a bit shorter in my component code

import Children from '../utils/children';
Children.filter(props.children, item => 'header' === item.type)

I'd suggest copying the Children object instead of mutating it so it's clearer when you're using your util and when you're not.

Thanks for the advice @spicyj !

Here's the code updated:

import { Children } from 'react';

export default Object.assign({}, Children, {
  filter(children, filterFn) {
    return Children
      .toArray(children)
      .filter(filterFn);
  }
});

@spicyj

It's true that Facebook has existing components that branch based on the type (or position) of a child, but it's usually a better idea to pass separate components explicitly.

Not always.
For example, I've got a dropdown menu which wraps each child into an <li/> and when encountering null it just throws an exception (presumably in React.Children.map).
Why does React.Children.map even have those empty children lol.
Broken design as for me.
And React.Children.toArray(children).map() doesn't work because is somehow messes keys

Hi!

I've made a quick small library with more functions for Children structure here

https://github.com/fernandopasik/react-children-utilities/blob/master/src/index.js

There are also recursive ways to do find, map and more stuff. Check them out, and let me know if there are more I can add.

Thanks!

I know this issue is old and closed, but I got here while googling how to do this, and thought I'd leave a note for posterity, since I found an easy way:

You can use React.Children.map to approximate filter if you just return nothing when the condition is false, and return the child when the condition is true:

filterChildren(children, conditionTester) {
    return React.Children.map(children, child => {
        if (!conditionTester(child)) {
            return;
        }
        return child;
    });
}

You can always just do:

const children = React.Children.map(this.props.children, child => child);

// and then

children.filter(someCondition);
children.some(anotherCondition);

or attach these directly on React.children().

I know this issue is old and closed, but I got here while googling how to do this, and thought I'd leave a note for posterity, since I found an easy way:

You can use React.Children.map to approximate filter if you just return nothing when the condition is false, and return the child when the condition is true:

filterChildren(children, conditionTester) {
    return React.Children.map(children, child => {
        if (!conditionTester(child)) {
            return;
        }
        return child;
    });
}

Wouldn't this give nulls in your array?

import MyComponent from './..'

filter(children, (child) => { child.type === MyComponent; })

Also if Children is FC component then there you can find out the Chilld.type.displayName also could be used for filtring.... the exmaple above works if Wrapper is a React.Component for children...

Was this page helpful?
0 / 5 - 0 ratings