Reputation: 122
I have a React parent component that holds state for the whole app. I want to pass a function down to a child component to so that when a button is clicked in that child then it changes the parent's state. Now I can do this for one layer, however when I've tried to pass the function down to a further layer (so a child component passing down a function it received as a prop) my app falls over with the following error:
TypeError: Cannot read property 'props' of undefined
Is it possible to pass this function down through multiple layers?
In my lowest level I don't have constructor set up as I thought that was only needed it I needed to initiate state in the child component, is that right?
I've detailed below the relevant parts of my code:
Parent:
class App extends Component {
constructor() {
super();
this.changeDisplay = this.changeDisplay.bind(this)
this.handleAddTicket = this.handleAddTicket.bind(this)
this.handleDeleteTicket = this.handleDeleteTicket.bind(this)
this.state = {...}
...
}
handleDeleteTicket(data){
console.log(data)
} ....
First Child
class Board extends React.Component {
return (
<Container>
<Row>
<Col sm={4}>
<Todo tasks={todoTasks} deleteTicket={this.props.deleteTicket}/>
</Col>
</Row>
</Container>
)
}
Second Child:
class Todo extends React.Component {
render() {
todoTicketsAr = this.props.tasks.map(function (obj, i) {
return <Ticket key={i} data={obj} deleteTicket={this.props.deleteTicket}></Ticket>
})
}
return (
<div>
<h2>To do:</h2>
{todoTicketsAr}
</div>
)
}
So I'm binding this at the parent level but nowhere else, is that right?
Upvotes: 2
Views: 4589
Reputation: 5584
Here's an example of how to do it with prop drilling and context API. I would use the Context API example because you don't need to keep prop drilling when you need to use different parts of the state within different components.
I have also provided a prop drilling example if you want to see that working and compare the two solutions.
const Global = React.createContext({});
class Store extends React.Component {
static Consumer = Global.Consumer;
state = {
value: 'bacon',
};
changeValue = (value) => this.setState({ value });
render() {
const { value } = this.state;
const { changeValue } = this;
return (
<Global.Provider value={{
value,
changeValue,
}}>
{this.props.children}
</Global.Provider>
)
}
}
class Child extends React.Component {
state = {
text: '',
};
handleChange = (evt) => this.setState({ text: evt.target.value });
render() {
const { text } = this.state;
const { handleChange } = this;
return (
<Store.Consumer>
{({ value, changeValue }) => (
<div>
<h3>Value is {value}</h3>
<h5>Type a new value and submit</h5>
<input value={text} onChange={handleChange} />
<button onClick={() => changeValue(text)}>Submit</button>
</div>
)}
</Store.Consumer>
)
}
}
const Layout = () => (
<div>
<h5>An example component that is not exposed to context</h5>
<Child/>
</div>
)
const Main = () => (
<Store>
<h2>React Context API example</h2>
<Layout/>
</Store>
);
ReactDOM.render(<Main/>, document.body);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
const ChildOfChild = ({ toggleDisplay }) => (
<div>
<button onClick={toggleDisplay}>Toggle in Child of Child</button>
</div>
);
const Child = ({ toggleDisplay }) => (
<div>
<button onClick={toggleDisplay}>Toggle in Child</button>
<ChildOfChild toggleDisplay={toggleDisplay} />
</div>
);
class Main extends React.Component {
state = {
display: true,
};
toggleDisplay = () => {
this.setState((prevState) => ({
display: !prevState.display,
}));
};
render() {
const { toggleDisplay } = this;
return (
<div>
<h2>React Prop drilling Example</h2>
<pre>{this.state.display.toString()}</pre>
<button onClick={this.toggleDisplay}>Parent</button>
<Child toggleDisplay={toggleDisplay} />
</div>
);
}
}
ReactDOM.render(
<Main/>,
document.body
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Upvotes: 3
Reputation: 39
The only time that would happen is because this
is no longer bound correctly. One common situation I could give is:
The will throw an error
class App extends Component {
someClickHandler() {
/**
* This line will throw an error onClick
* of the button because `this` is not bound to the right thing.
* If you log out `this` you'll probably see the click event object.
*/
console.log(this.state);
}
render() {
return (
<button onClick={this.someHandler}>Random button</button
)
}
}
To properly bind this
you have to do either
<button onClick={() => this.someHandler()}>Random button</button>
// or
<button onClick={this.someHandler.bind(this)}>Random button</button>
Upvotes: 0