118

I'm looking for the easiest solution to pass data from a child component to his parent.

I've heard about using Context, pass trough properties or update props, but I don't know which one is the best solution.

I'm building an admin interface, with a PageComponent that contains a ChildComponent with a table where I can select multiple line. I want to send to my parent PageComponent the number of line I've selected in my ChildComponent.

Something like that :

PageComponent :

<div className="App">
  <EnhancedTable />         
  <h2>count 0</h2>
  (count should be updated from child)
</div>

ChildComponent :

 const EnhancedTable = () => {
     const [count, setCount] = useState(0);
     return (
       <button onClick={() => setCount(count + 1)}>
          Click me {count}
       </button>
     )
  };

I'm sure it's a pretty simple thing to do, I don't want to use redux for that.

Kaherdin
  • 2,055
  • 3
  • 23
  • 34
  • How about using hooks on ```PageComponent``` and send it (both the ```count``` and ```setCount``` via props to ```EnhancedTable``` component? – mfakhrusy Apr 17 '19 at 11:50

7 Answers7

127

A common technique for these situations is to lift the state up to the first common ancestor of all the components that needs to use the state (i.e. the PageComponent in this case) and pass down the state and state-altering functions to the child components as props.

Example

const { useState } = React;

function PageComponent() {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount(count + 1)
  }

  return (
    <div className="App">
      <ChildComponent onClick={increment} count={count} />         
      <h2>count {count}</h2>
      (count should be updated from child)
    </div>
  );
}

const ChildComponent = ({ onClick, count }) => {
  return (
    <button onClick={onClick}>
       Click me {count}
    </button>
  )
};

ReactDOM.render(<PageComponent />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
Tholle
  • 108,070
  • 19
  • 198
  • 189
  • 4
    Yes, but I need the increment function to be inside the ChildComponent. And then update the Parent with the result. – Kaherdin Apr 17 '19 at 12:00
  • 2
    The problem is if i have const increment (value) => { console.log(value) -> real value I've got from child c setState(value) console.log(state) -> undefined } But after the second callbackCall I got the prev state value. How to fix this ? – Hubi Apr 10 '20 at 09:12
  • In TypeScript, what should the type be in ChildComponent params? If I use eg. `export const Child = ( myFunc: Function) => {...}`, then the component instance `` in parent will throw a compiler error `Property 'myFunc' does not exist on type 'IntrinsicAttributes & Function'`. Replacing `myFunc: Function` with `myFunc: any` will "fix" it but it's not really a good practice. – Juha Untinen Sep 16 '22 at 09:30
  • Where does it pass the data back? – Smart Coder Feb 24 '23 at 18:44
87

You can create a method in your parent component, pass it to child component and call it from props every time child's state changes, keeping the state in child component.

    const EnhancedTable = ({ parentCallback }) => {
        const [count, setCount] = useState(0);
        
        return (
            <button onClick={() => {
                const newValue = count + 1;
                setCount(newValue);
                parentCallback(newValue);
            }}>
                 Click me {count}
            </button>
        )
    };

    class PageComponent extends React.Component { 
        callback = (count) => {
            // do something with value in parent component, like save to state
        }

        render() {
            return (
                <div className="App">
                    <EnhancedTable parentCallback={this.callback} />         
                    <h2>count 0</h2>
                    (count should be updated from child)
                </div>
            )
        }
    }
crg
  • 4,284
  • 2
  • 29
  • 57
maktel
  • 1,252
  • 9
  • 7
  • but now could `this.callback` trigger a re-render unnecessarily? – ecoe Jul 24 '19 at 18:14
  • 1
    @ecoe of course it could, depends on what callback does exactly. Calling some external functions/dispatching actions should be relatively safe, setting parent's state – not so much. Definitely this is not the cleanest code, so approaches from other answers should be employed if possible. – maktel Jul 25 '19 at 08:50
70

To make things super simple you can actually share state setters to children and now they have the access to set the state of its parent.

example: Assume there are 4 components as below,

function App() {
  return (
    <div className="App">
      <GrandParent />
    </div>
  );
}

const GrandParent = () => {
  const [name, setName] = useState("i'm Grand Parent");
  return (
    <>
      <div>{name}</div>
      <Parent setName={setName} />
    </>
  );
};

const Parent = params => {
  return (
    <>
      <button onClick={() => params.setName("i'm from Parent")}>
        from Parent
      </button>
      <Child setName={params.setName} />
    </>
  );
};

const Child = params => {
  return (
    <>
      <button onClick={() => params.setName("i'm from Child")}>
        from Child
      </button>
    </>
  );
};

so grandparent component has the actual state and by sharing the setter method (setName) to parent and child, they get the access to change the state of the grandparent.

you can find the working code in below sandbox, https://codesandbox.io/embed/async-fire-kl197

Nikhil Seepana
  • 701
  • 5
  • 3
3

IF we Have Parent Class Component and Child function component this is how we going to access child component useStates hooks value :--

class parent extends Component() {
 
  constructor(props){
    super(props)
    this.ChildComponentRef = React.createRef()   
  }
  
  render(){
    console.log(' check child stateValue: ', 
    this.ChildComponentRef.current.info);
    return (<> <ChildComponent ref={this.ChildComponentRef} /> </>)
 }
}

Child Component we would create using

React.forwardRef((props, ref) => (<></>))

. and

useImperativeHandle(ref, createHandle, [deps])

to customizes the instance value that is exposed to parent components

const childComponent = React.forwardRef((props, ref) => {
   const [info, setInfo] = useState("")

   useEffect(() => {
      axios.get("someUrl").then((data)=>setInfo(data))
   })

  useImperativeHandle(ref, () => {
      return {
          info: info
      }
  })
  return (<> <h2> Child Component <h2> </>)
 })
AmitNayek
  • 158
  • 1
  • 9
0

I had to do this in type script. The object-oriented aspect would need the dev to add this callback method as a field in the interface after inheriting from parent and the type of this prop would be Function. I found this cool!

Niraj Motiani
  • 71
  • 1
  • 8
0

Here's an another example of how we can pass state directly to the parent.

I modified a component example from react-select library which is a CreatableSelect component. The component was originally developed as class based component, I turned it into a functional component and changed state manipulation algorithm.

import React, {KeyboardEventHandler} from 'react';
import CreatableSelect from 'react-select/creatable';
import { ActionMeta, OnChangeValue } from 'react-select';

const MultiSelectTextInput = (props) => {
    const components = {
        DropdownIndicator: null,
    };

    interface Option {
        readonly label: string;
        readonly value: string;
    }

    const createOption = (label: string) => ({
        label,
        value: label,
    });

    const handleChange = (value: OnChangeValue<Option, true>, actionMeta: ActionMeta<Option>) => {
        console.group('Value Changed');
        console.log(value);
        console.log(`action: ${actionMeta.action}`);
        console.groupEnd();
        props.setValue(value);
    };

    const handleInputChange = (inputValue: string) => {
        props.setInputValue(inputValue);
    };

    const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
        if (!props.inputValue) return;

        switch (event.key) {
            case 'Enter':
            case 'Tab':
                console.group('Value Added');
                console.log(props.value);
                console.groupEnd();
                props.setInputValue('');
                props.setValue([...props.value, createOption(props.inputValue)])
                event.preventDefault();
        }
    };

    return (
        <CreatableSelect
            id={props.id}
            instanceId={props.id}
            className="w-100"
            components={components}
            inputValue={props.inputValue}
            isClearable
            isMulti
            menuIsOpen={false}
            onChange={handleChange}
            onInputChange={handleInputChange}
            onKeyDown={handleKeyDown}
            placeholder="Type something and press enter..."
            value={props.value}
        />
    );
};

export default MultiSelectTextInput;

I call it from the pages of my next js project like this

import MultiSelectTextInput from "../components/Form/MultiSelect/MultiSelectTextInput";

const NcciLite = () => {
    const [value, setValue] = useState<any>([]);
    const [inputValue, setInputValue] = useState<any>('');

    return (
        <React.Fragment>
            ....
            <div className="d-inline-flex col-md-9">
                <MultiSelectTextInput
                    id="codes"
                    value={value}
                    setValue={setValue}
                    inputValue={inputValue}
                    setInputValue={setInputValue}
                />
            </div>
            ...
        </React.Fragment>
    );
};

As seen, the component modifies the page's (parent page's) state in which it is called.

Ahmet Firat Keler
  • 2,603
  • 2
  • 11
  • 22
-8

I've had to deal with a similar issue, and found another approach, using an object to reference the states between different functions, and in the same file.

import React, { useState } from "react";

let myState = {};

const GrandParent = () => {
  const [name, setName] = useState("i'm Grand Parent");
  myState.name=name;
  myState.setName=setName;
  return (
    <>
      <div>{name}</div>
      <Parent />
    </>
  );
};
export default GrandParent;

const Parent = () => {
  return (
    <>
      <button onClick={() => myState.setName("i'm from Parent")}>
        from Parent
      </button>
      <Child />
    </>
  );
};

const Child = () => {
  return (
    <>
      <button onClick={() => myState.setName("i'm from Child")}>
        from Child
      </button>
    </>
  );
};
  • 3
    I'm really not sure if that's the correct way to be using useState. I would recommend following the official documentation closely (https://reactjs.org/docs/hooks-state.html). in your answer above you dont need the "myState" you can just use the name and setName variables from useState. Just think if you want to get a variable value i.e "name" use the first variable. if you want to set a variable use the second variable i.e setName. Fair play, good job for trying to coming up with your own solution, but sometimes it will end in a mess. Stick to best practices and official docs. – Andrew Irwin Dec 11 '19 at 16:50