32

Quick question for react gurus ;)

React.Children.only is one of its top-level apis, and is very commonly used by react-redux (<Provider />) and React Router (<Router />) to inject store/router as context, what's the reason behind this, why not simply return props.children? Seems something to do with JSX?

EDIT: Please don't explain what is React.Children.only, i am asking for why using it instead of props.children, which seems more powerful/flexible.

Allen
  • 4,431
  • 2
  • 27
  • 39
  • the docs seem self explanatory - a nice way to verify you only get one child – Andy Ray Feb 17 '18 at 04:21
  • the reason is why you wanna define an interface with only one child, it's common in app's perspective to return props.children as an array of elements right? – Allen Feb 17 '18 at 04:23

3 Answers3

26

As pointed out in the docs

Verifies that children has only one child (a React element) and returns it. Otherwise this method throws an error.

So now why is it helpful over just using props.children?

The main reason is it's throwing an error, thus halting the whole dev flow, so you cannot skip it.

This is a handy util that enforces a rule of having specifically and only one child.

Of course, you could use propTypes, but that will only put a warning in the console, that you might as well miss.

One use case of React.Children.only can be to enforce specific declarative interface that should consist of one logical Child component:

class GraphEditorEditor extends React.Component {
  componentDidMount() {
    this.props.editor.makeEditable();
    // and all other editor specific logic
  }

  render() {
    return null;
  }
}

class GraphEditorPreview extends React.Component {
  componentDidMount() {
    this.props.editor.makePreviewable();
    // and all other preview specific logic
  }

  render() {
    return null;
  }
}

class GraphEditor extends React.Component {
  static Editor = GraphEditorEditor;
  static Preview = GraphEditorPreview;
  wrapperRef = React.createRef();

  state = {
    editorInitialized: false
  }

  componentDidMount() {
    // instantiate base graph to work with in logical children components
    this.editor = SomeService.createEditorInstance(this.props.config);
    this.editor.insertSelfInto(this.wrapperRef.current);

    this.setState({ editorInitialized: true });
  }

  render() {
    return (
      <div ref={this.wrapperRef}>
        {this.editorInitialized ?
          React.Children.only(
            React.cloneElement(
              this.props.children, 
              { editor: this.editor }
            )
          ) : null
        }
      </div>
    );
  }
}

which can be used like this:

class ParentContainer extends React.Component {
  render() {
    return (
      <GraphEditor config={{some: "config"}}>
        <GraphEditor.Editor> //<-- EDITOR mode
      </GraphEditor>
    )
  }
}

// OR

class ParentContainer extends React.Component {
  render() {
    return (
      <GraphEditor config={{some: "config"}}>
        <GraphEditor.Preview> //<-- Preview mode
      </GraphEditor>
    )
  }
}

Hope this is helpful.

Karen Grigoryan
  • 5,234
  • 2
  • 21
  • 35
  • thanks for your answer, i think the reason you pointed out to restrict only one child can be handled by PropTypes.element, well propTypes doesn't enforce runtime safe, but that's another topic for typescript/flow. and i don't think it has anything to do with render prop, since both redux provider & react-router's Router didn't use this, but they do use React.Children.only. thanks anyways! – Allen Feb 18 '18 at 20:30
  • @Xlee I was not pointing out that these projects specifically do use render props, but a lot do. Here is one for example https://github.com/chenglou/react-radio-group/blob/9a992f3bbc1bffeb1dc993e42b0f4842ab299f42/index.jsx – Karen Grigoryan Feb 18 '18 at 20:55
  • @Xlee in regards to the enforcing, here is the commit in react-router explicitly outlining the purpose https://github.com/ReactTraining/react-router/commit/5a74d46a9fcf1b9663ba42b238ba6dd4584983ab – Karen Grigoryan Feb 18 '18 at 20:56
  • I mean i personally like render props a lot since it's cool and useful, but at the end you're gonna find they're just one of many component's patterns with pros & cons based on use case. Regarding that commit, it simply tells that React.Children.only can be used to enforcing single child, but it doesn't mean it's designed for this. My question is like why redux provider is enforcing its users to only pass one child and you're telling how they make this happen which i already know, right? – Allen Feb 18 '18 at 21:48
  • 2
    @Xlee no what I am telling is, they use `React.Children.only` because they want to process a single React.Element and not, say, an array. It's a simple safeguard, they don't want to handle logic for when you pass array, obviously. it's the only logical explanation that comes out of their code and the definition of `React.Children.only` itself. If you seek for any hidden meanings I think your best bet is with creators, ask Dan directly and let us all know :) Good luck. – Karen Grigoryan Feb 18 '18 at 22:47
  • Like your advice, dude – Allen Feb 18 '18 at 22:53
  • @KarenGrigoryan it does not work for me for render props pattern, and according to the doc it's expected to receive a single **react element**, not function – Sebastien Lorber Sep 20 '18 at 12:06
  • @SebastienLorber hey thanks for pointing out that example is incorrect, I copy pasted from https://mxstbr.blog/2017/02/react-children-deepdive/ which is a pretty popular author and article. I should've been more attentive, instead of blind copy paste, but now I changed it to my own example and hope it makes sense now. Thank you. – Karen Grigoryan Sep 20 '18 at 12:58
0

Before React 16, a Component's render method could not return an array of elements. When implementing a component that didn't introduce any of its own markup, you had no choice but to only accept a single child:

// We have to ensure that there's only one child, because returning an array
// from a component is prohibited.
const DisplayIf = ({children, condition}, {uniforms}) =>
    condition(uniforms) ? Children.only(children) : nothing;
DisplayIf.contextTypes = BaseField.contextTypes;

Now you can simply return props.children, requiring the caller to include keys on array elements, or return <>{props.children}</>, using a Fragment to avoid returning an array:

const DisplayIf = ({children, condition}, {uniforms}) =>
    condition(uniforms) ? <>{children}</> : nothing;
DisplayIf.contextTypes = BaseField.contextTypes;
BudgieInWA
  • 2,184
  • 1
  • 17
  • 31
  • Just note that this `` is a bad idea - the `SomeComponent` is always rendered and if the condition is not met, it is just thrown away by DisplayIf. Therefore if SomeComponent is expensive to render or can produce errors when it's not expected to show up, this can cause problems. – amik Oct 14 '19 at 13:32
0

I have just found out a case of using Children.only while implementing Tooltip component: https://github.com/chakra-ui/chakra-ui/blob/2456a7090439ebd1a1d1cdee36daa472d12ce6f7/packages/components/tooltip/src/tooltip.tsx#L113.

When there is an unknown number of children, it will be tricky for Tooltip component itself to set the ref. As a result, it will be really handy to have Children.only here.

Yi-Pei Huang
  • 131
  • 1
  • 7