SPG
SPG

Reputation: 6197

How to call parent function from custom child component?

I have two components, parent and child.

Parent:

class Parent extends React.Component {
  constructor(){
    super()
    this.state = {
      select: false
    }
  }
  isSelect(){
    this.setState({...this.state, select: !this.state.select})
  }
  render(){
    const { Header, children } = this.props
    return (
      <>
        <Header isSelect={this.isSelect} />
      </>
    )
  }
}

Child:

class Child extends React.Component {
  render(){
    const {title, isSelect} = this.props
    /*problem here! can not get isSelect*/
    return(
      <div>
        <p>{title}</p>
        <button onClick={this.isSlect}>choose</button>
        /*problem here! isSelect*/
      </div>
    )
  }
}

In use:

<Parent Header={<Child title='just for test' />} />

The components can be rendered but not for click event. I want to set the isSlect function automatically for Child component, cause it just call the Parent function and revert the Boolean value. so It's no meaning to pass it again in use.

The problem is How can I pass this isSelect? It seems like isSelect={this.isSelect} is overwritten from <Header isSelect={this.isSelect} />.

Upvotes: 1

Views: 110

Answers (2)

Treycos
Treycos

Reputation: 7492

You could change the behavior of Header to be a function passing the isSelect callback in its argument :

render() {
    const { Header, children } = this.props

    return Header(this.isSelect)
}

The component can now assign the received function however you want :

<Parent Header={clicked => <Child title='just for test' isSelect={clicked} />} />

Working live example :

class Parent extends React.Component {
    constructor(props) {
        super(props) //Don't forget to send the props in super
        this.state = {
            select: false
        }
    }

    isSelect = () => { //Arrow function to avoid having to bind it
        this.setState({ ...this.state, select: !this.state.select })
        console.log('Selected')
    }

    render() {
        const { Header, children } = this.props

        return Header(this.isSelect)
    }
}

class Child extends React.Component {
    render() {
        const { title, isSelect } = this.props

        return (
            <div>
                <p>{title}</p>
                <button onClick={isSelect}>choose</button> {/* Careful, you do not need to add 'this.' */}
            </div>
        )
    }
}

ReactDOM.render(<Parent Header={clicked => <Child title='just for test' isSelect={clicked} />} />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<div id='root'>

Upvotes: 1

helb
helb

Reputation: 3234

JS class methods are not bound by default, so if you pass isSelect like you did in your code, this will be undefined (and this.setState won't work).

You can either pass an anonymous arrrow function calling isSelect to the child compoment:

<Child isSelect={() => this.isSelect()} />

or just bind it in the Parent contructor:

this.isSelect = this.isSelect.bind(this)

or use public class fields (if you use Babel or some other transpiler, they are still experimental):

class Parent extends React.Component {
    …

    isSelect = () => {
        this.setState(…)
    }
}

React docs: handling events

Working example:

class Parent extends React.Component {
  constructor(){
    super()
    this.state = {
      select: false
    }
    this.isSelect = this.isSelect.bind(this)
  }
  isSelect(){
    this.setState({...this.state, select: !this.state.select})
  }
  render(){
    const { Header, children } = this.props
    return (
      <div>
        <Child isSelect={this.isSelect} />
        <Child isSelect={() => this.isSelect()} /> {/* this one will work even without .bind in the constructor */}
        <code>{JSON.stringify(this.state, null, 2)}</code>
      </div>
    )
  }
}

class Child extends React.Component {
  render(){
    return(
      <div>
        <button onClick={this.props.isSelect}>choose</button>
      </div>
    )
  }
}



ReactDOM.render(
  (<Parent/>),
  document.querySelector("#root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 0

Related Questions