Velvet Star Monitor

Standout celebrity highlights with iconic style.

news

ReactJS: setState on parent inside child component

Writer Emily Wong

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.

4

8 Answers

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.

5

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;
8

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 . The page also works through an example.

You could create an addTodo function in the parent component, bind it to that context, pass it to the child component and call it from there.

// in Todos
addTodo: function(newTodo) { // add todo
}

Then, in Todos.render, you would do

<TodoForm addToDo={this.addTodo.bind(this)} />

Call this in TodoForm with

this.props.addToDo(newTodo);
1
parentSetState={(obj) => { this.setState(obj) }}
1

I found the following working and simple solution to pass arguments from a child component to the parent component:

//ChildExt component
class ChildExt extends React.Component { render() { var handleForUpdate = this.props.handleForUpdate; return (<div><button onClick={() => handleForUpdate('someNewVar')}>Push me</button></div> ) }
}
//Parent component
class ParentExt extends React.Component { constructor(props) { super(props); var handleForUpdate = this.handleForUpdate.bind(this); } handleForUpdate(someArg){ alert('We pass argument from Child to Parent: \n' + someArg); } render() { var handleForUpdate = this.handleForUpdate; return (<div> <ChildExt handleForUpdate = {handleForUpdate.bind(this)} /></div>) }
}
if(document.querySelector("#demo")){ ReactDOM.render( <ParentExt />, document.querySelector("#demo") );
}

Look at JSFIDDLE

If you are working with a class component as parent, one very simple way of passing a setState to a child is by passing it within an arrow function. This works as it sets a hoisted environment that can be passed around:

class ClassComponent ... { modifyState = () =>{ this.setState({...}) } render(){ return <><ChildComponent parentStateModifier={modifyState} /></> }
}

For anyone using useState inside Arrow Functional Components with TypeScript enabled, here's a simple example of how you can pass the parent's state setter function to the child, and invoke it to set the parent's state at the appropriate time from the child-

Sample

Parent component:

import {useState} from "react";
const ParentComponent = () => { const [hasParentStateChange, setHasParentStateChange] = useState<boolean>(false); return ( <div> Parent's view: {String(hasParentStateChange)} <ChildComponent hasParentStateChange={hasParentStateChange} setHasParentStateChange={setHasParentStateChange} // <---- Passing parent's state setter over /> </div> );
}

Child component:

interface PropsDefinition { hasParentStateChange: boolean; setHasParentStateChange(data: boolean): void;
}
const ChildComponent = (props: PropsDefinition) => { return ( <div> <div> Child's view: {String(props.hasParentStateChange)} </div> <button onClick={() => props.setHasParentStateChange(!props.hasParentStateChange)} // <---- Invoking parent's state setter > Toggle parent state from child </button> </div> );
}

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy