130

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.

Pavithra
  • 1,303
  • 2
  • 9
  • 6
  • 2
    does this help http://stackoverflow.com/questions/24147331/react-the-right-way-to-pass-form-element-state-to-sibling-parent-elements ? – Dhiraj Mar 17 '15 at 14:10
  • Just gonna spam here... https://www.npmjs.com/package/react-event-observer – jujiyangasli Jun 01 '17 at 19:12
  • I'm getting error `setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the MyModal component.` – Matt Oct 29 '17 at 23:01
  • I am getting the same error that I cannot setState on an unmounted component. Was there a workaround for this? – Kevin Burton Jun 20 '20 at 22:44

9 Answers9

114

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.

akseli
  • 79
  • 8
Deepak
  • 2,481
  • 2
  • 23
  • 28
22

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;
MrSegFaulty
  • 535
  • 6
  • 10
NicoWheat
  • 2,157
  • 2
  • 26
  • 34
  • You can use this app with create-react-app, and replace all of the code in App.js with the code above. – NicoWheat Sep 01 '19 at 21:00
  • Hi, I'm new to react and was wondering: Is it necessary to use `useEffect` ? Why do we need to have the data stored in both parent and child state ? – TOPKAT Feb 09 '20 at 11:20
  • 2
    The examples are not intended to show why we need to store data in both parent and child- most of the time you do not need to. But, if you found yourself in a situation that the child should set the parent state, this is how you could do it. useEffect is necessary if you want to set the parent state AS AN EFFECT of the childState changing. – NicoWheat Feb 09 '20 at 18:48
  • 1
    @GLAND_PROPRE I could have been more clear in my answer: no `useEffect` is not necessary. We could call `setParentState(e.target.value)` inside the `onSliderChangeHandler` function directly. – NicoWheat Aug 17 '21 at 14:04
  • Why did you add the wrapper around `setParentState`? – Ian Dunn Sep 10 '21 at 16:16
  • 2
    @IanDunn I believe `useCallback` prevents a new function from being created on every render. Here is a good explanation of when it is necessary and when it is not. https://dmitripavlutin.com/dont-overuse-react-usecallback/ – NicoWheat Sep 10 '21 at 17:33
  • if anyone doesn't mind answering, is it bad practice to mutate the parent component's state? – Krusty the Clown May 04 '22 at 00:56
  • 1
    @TheCornInspector It is not a bad practice. If you end up doing it too often, a better pattern is to use either `context` reactjs.org/docs/context.html or, for more advanced applications, a state manager like `Redux` react-redux.js.org/ – NicoWheat May 04 '22 at 17:21
15

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.

10

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);
rallrall
  • 4,760
  • 1
  • 15
  • 15
  • 1
    This was so useful. Without doing `bind(this)` at the time of passing function, it was throwing error no such function `this.setState is not a function`. – pratpor Jul 02 '20 at 12:36
8

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>
  );
}
dian jin
  • 195
  • 3
  • 12
4
parentSetState={(obj) => this.setState(obj)}
dezman
  • 18,087
  • 10
  • 53
  • 91
  • 10
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – Nic3500 Aug 02 '18 at 01:11
2

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

Roman
  • 19,236
  • 15
  • 93
  • 97
1

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} /></>
    }
}
Julio Pereira
  • 70
  • 1
  • 7
1

As of v18.2, The React official documentation shows you how to do this with the combination of useReducer and useContext.

https://react.dev/learn/scaling-up-with-reducer-and-context

Here is an idea how to implement:

import React, { createContext, useContext, useReducer, useState } from 'react';

const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);

function TasksProvider({ children }) {
    const [tasks, dispatch] = useReducer(tasksReducer, []);
    return (
        <TasksContext.Provider value={tasks}>
        <TasksDispatchContext.Provider value={dispatch}>
            {children}
        </TasksDispatchContext.Provider>
        </TasksContext.Provider>
    );
}

function useTasks() { return useContext(TasksContext); }
function useTasksDispatch() { return useContext(TasksDispatchContext); }

function tasksReducer(tasks, action) {
    switch (action.type) {
        case 'added': {
            return [...tasks, { id: action.id, text: action.text }];
        }
        …
    }
}

let nextId = 0;
function AddTask() {
    const [text, setText] = useState('');
    const dispatch = useTasksDispatch();
    return (
        <>
            <input value={text} onChange={e => setText(e.target.value)} />
            <button onClick={() => {
                setText('');
                dispatch({type: 'added', id: nextId++, text: text })
            }}>Add</button>
        </>
    )
}

function TaskList() {
    const tasks = useTasks();
    return (
        {tasks.map(task => (<div key={task.id}>{task.text}</div))}
    );
}

export default function TaskApp() {
    return (
        <TasksProvider>
            <AddTask />
            <TaskList />
        </TasksProvider>
    );
  }
user3445724
  • 126
  • 1
  • 3