What is the recommended pattern for doing a setState on a parent from a child component.
var Todos = React.createClass({
getInitialState: function() {
return {
todos: [
"I am done",
"I am not done"
]
}
},
render: function() {
var todos = this.state.todos.map(function(todo) {
return <div>{todo}</div>;
});
return <div>
<h3>Todo(s)</h3>
{todos}
<TodoForm />
</div>;
}
});
var TodoForm = React.createClass({
getInitialState: function() {
return {
todoInput: ""
}
},
handleOnChange: function(e) {
e.preventDefault();
this.setState({todoInput: e.target.value});
},
handleClick: function(e) {
e.preventDefault();
//add the new todo item
},
render: function() {
return <div>
<br />
<input type="text" value={this.state.todoInput} onChange={this.handleOnChange} />
<button onClick={this.handleClick}>Add Todo</button>
</div>;
}
});
React.render(<Todos />, document.body)
I have an array of todo items which is maintained in the parent's state.
I want to access the parent's state and add a new todo item, from the TodoForm
's handleClick
component.
My idea is to do a setState on the parent, which will render the newly added todo item.
In your parent, you can create a function like addTodoItem
which will do the required setState and then pass that function as props to the child component.
var Todos = React.createClass({
...
addTodoItem: function(todoItem) {
this.setState(({ todos }) => ({ todos: { ...todos, todoItem } }));
},
render: function() {
...
return <div>
<h3>Todo(s)</h3>
{todos}
<TodoForm addTodoItem={this.addTodoItem} />
</div>
}
});
var TodoForm = React.createClass({
handleClick: function(e) {
e.preventDefault();
this.props.addTodoItem(this.state.todoInput);
this.setState({todoInput: ""});
},
...
});
You can invoke addTodoItem
in TodoForm's handleClick. This will do a setState on the parent which will render the newly added todo item. Hope you get the idea.
Fiddle here.
For those who are maintaining state with the React Hook useState
, I adapted the above suggestions to make a demo slider App below. In the demo app, the child slider component maintains the parent's state.
The demo also uses useEffect
hook. (and less importantly, useRef
hook)
import React, { useState, useEffect, useCallback, useRef } from "react";
//the parent react component
function Parent() {
// the parentState will be set by its child slider component
const [parentState, setParentState] = useState(0);
// make wrapper function to give child
const wrapperSetParentState = useCallback(val => {
setParentState(val);
}, [setParentState]);
return (
<div style={{ margin: 30 }}>
<Child
parentState={parentState}
parentStateSetter={wrapperSetParentState}
/>
<div>Parent State: {parentState}</div>
</div>
);
};
//the child react component
function Child({parentStateSetter}) {
const childRef = useRef();
const [childState, setChildState] = useState(0);
useEffect(() => {
parentStateSetter(childState);
}, [parentStateSetter, childState]);
const onSliderChangeHandler = e => {
//pass slider's event value to child's state
setChildState(e.target.value);
};
return (
<div>
<input
type="range"
min="1"
max="255"
value={childState}
ref={childRef}
onChange={onSliderChangeHandler}
></input>
</div>
);
};
export default Parent;
These are all essentially correct, I just thought I would point to the new(ish) official react documentation which basically recommends:-
There should be a single “source of truth” for any data that changes in a React application. Usually, the state is first added to the component that needs it for rendering. Then, if other components also need it, you can lift it up to their closest common ancestor. Instead of trying to sync the state between different components, you should rely on the top-down data flow.
See https://reactjs.org/docs/lifting-state-up.html. The page also works through an example.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With