0

It seems just recently React won't treat this.props.children as a function as it did in the past.

I just coded a Modal component where its closeModal function should be passed to the children,

render() {
  return (
    <Modal>
      {
        (closeModal) => {
          return (<span onClick={closeModal}>Click to close modal</span>)
        }
      }
    </Modal>
  )
}

Modal looks like this

class Modal extends React.Component {
  constructor(props) {
    this.state = { show: true }

    this.close = this.closeModal.bind(this)
  }

  closeModal() {
    this.setState({ show: false })
  }

  render() {
    if (!this.state.show)
      return null

    return (
      <div>
        { this.props.children }
      </div>
    )
  }
}

I tried to pass function as a prop via this.props.children({ closeModal: this.closeModal }), guess what, this.props.children is not a function according to latest React 16.9.

As a reference for the folks working with GraphQL, I see Apollo client's <Mutation> and <Query> working quite much the same way.

How can it be achieved?

Edit: Why not a duplicate? Because other answers rely on this.props.children as function whereas recently React is now rendering error demanding a new approach to this issue: TypeError: this.props.children is not a function

Cássio
  • 649
  • 1
  • 7
  • 17
  • 2
    It's called [Render props](https://reactjs.org/docs/render-props.html) and you would need to explicitly call `this.props.children(this.close)`. – Emile Bergeron Sep 05 '19 at 20:36
  • Possible duplicate of [How to pass props to {this.props.children}](https://stackoverflow.com/questions/32370994/how-to-pass-props-to-this-props-children) – Emile Bergeron Sep 05 '19 at 20:36
  • Took a good look at that before posting, however React renders TypeError: this.props.children is not a function – Cássio Sep 05 '19 at 21:44
  • I tested on a previous React 16.8 and that worked, but does not on the latest, it may be a bug or something, gonna report it. Thanks for the answer! – Cássio Sep 05 '19 at 21:56
  • 2
    As pointed out in [Sung M. Kim's answer](https://stackoverflow.com/a/57812813/1218980), you forgot to call `super(props)` in the constructor. Without it, `this.props` isn't populated and `this.props.children` is then not a function. – Emile Bergeron Sep 06 '19 at 01:16
  • Smart move, didn't see that coming. Thanks so much! – Cássio Sep 06 '19 at 01:36

2 Answers2

2

I've answered the updates needed to show what is wrong and how it can be changed in-line below.

class Modal extends React.Component {
  constructor(props) {
    // 1️⃣ Make sure to make `props` available in this component.
    // This call is what makes `this.props` call to be available within `Modal`.
    super(props);

    this.state = { show: true };

    // 2️⃣ Assign a newly bound method to the matching class method
    // this.close = this.closeModal.bind(this);
    this.closeModal = this.closeModal.bind(this);
  }

  closeModal() {
    this.setState({ show: false });
  }

  render() {
    if (!this.state.show) return null;

    // 3️⃣ Pass the modal handler to children
    // If you had kept `this.close`, then when you pass the handler
    // "{ closeModal: this.close }" instead of "{ closeModal: this.closeModal }"
    return <div>{this.props.children({ closeModal: this.closeModal })}</div>;
  }
}

function App() {
  return (
    <div className="App">
      <Modal>
        {/* 4️⃣ destructure `closeModal` */}
        {({ closeModal }) => {
          return <button onClick={closeModal}>Click to close modal</button>;
        }}
      </Modal>
    </div>
  );
}

As Emile Bergeron has kindly pointed out, you can pass this.props.children(this.close) instead of an object but I found it easier to read/use.

You can fork and try or run the code snippet below~
Thanks Emile Bergeron for the suggestion in the comment~
Edit so.answer.57812519

class Modal extends React.Component {
  constructor(props) {
    super(props);

    this.state = { show: true };

    // this.close = this.closeModal.bind(this);
    this.closeModal = this.closeModal.bind(this);
  }

  closeModal() {
    this.setState({ show: false }, () => {
      console.log(`Modal closed`);
    });
  }

  render() {
    if (!this.state.show) return null;

    return <div>{this.props.children({ closeModal: this.closeModal })}</div>;
  }
}

function App() {
  return (
    <div className="App">
      <Modal>
        {({ closeModal }) => {
          return (
            <button type="button" onClick={closeModal}>
              Click to close modal
            </button>
          );
        }}
      </Modal>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

<div id="root"></div>
dance2die
  • 35,807
  • 39
  • 131
  • 194
0

try this one

class Modal extends React.Component {

  constructor(props) {
    this.state = { show: true }

    this.close = this.closeModal.bind(this)
  }

  closeModal() {
    this.setState({ show: false })
  }

  render() {
    if (!this.state.show)
      return null

    return (
      <div>
        { this.props.children(this.close) }
      </div>
    )
  }
}
Max
  • 781
  • 7
  • 19