How to access particular child state from parent with multiple children?

Viewed 626

Let's say I have a parent component in React with 3 separate child components of the same component class (meaning, within class Parent I have 3 Child components). How do I access each child's state within the Parent component?

My initial thoughts are to have a separate variable for each Child's state that I want to access (I only want to access the filled variable for each child). But I feel that this is certainly a sloppy solution to something that is already in place with React, so would appreciate any pointers.

Example code below for illustration purposes.

const Parent = (props) => {

return (
<div>
    <Child/>
    <Child/>
    <Child/>
</div>
);
}


const Child = (props) => {
const [filled, setFilled] = useState(false);

return (
    <div></div>
);
}

Perhaps the better question is how do I access the particular child? And once accessed, how do I access its filled state (callback function)? I've read about useRef, is that where I should be looking here?

4 Answers

If what you are trying to do is report back to the parent the childs's state, you can do that by passing down from the parent, via props, a function to report that state back, as such:

const Parent = (props) => {

    const reportChildState = (value) =>{
        //do something with the child filled state
    }

    return (
    <div>
        <Child reportState={reportChildState}/>
        <Child reportState={reportChildState}/>
        <Child reportState={reportChildState}/>
    </div>
    );
}


const Child = (props) => {

    //in here you can call props.reportChildState(filled)
    const [filled, setFilled] = useState(false);

    return (
        <div></div>
    );
}

As far as I'm aware there isnt a way to access a child components state from the parent. The only solutions are to either pass the state object into the child as a prop, using the context API or using a thirst party state management such as redux.

I wouldnt use useref for accessing a child's state.

You actually wouldn't directly access the children to retrieve their states. What you want here is a Context that encompases the parent component. Check out the React Context API. In short, you can create a "context" that contains states that need to be shared between multiple components. Once the context is made, a Provider is also created with that context. This provider is a component that accepts a value prop. This prop contains an object of all the state values and setter functions within the context. Child components of a Provider component can use the useContext hook to retrieve the values and functions from the value prop of the Provider.

Code example:

MyContext.js

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

// Create the context and give it default values
export const MyContext = createContext(defaultValuesObject);

// We create a component that wraps around the provider, and is stateful. The states and their setters are placed into the provider, which is then returned.
const MyProvider = (props) => {
    const [firstName, setFirstName] = useState("")
    const [lastName, setLastName] = useState("")
    
    const dataToShare = {
        firstName, 
        setFirstName, 
        lastName, 
        setLastName,
    }

    // Return the context provider with the data already inside, and fill the children.
    return (
        <MyContext.Provider value={dataToShare}>
            {props.children}
        </MyContext.Provider>
    )

}

We have the provider now. In your parent component's code, use the useContext hook to give the component access to the values and functions stored inside the provider component.

MyParentComponent.jsx

import React, {useContext} from 'react';
import MyProvider, { MyContext } from 'MyContext'

const MyComponent = (props) => {
    // useContext returns the value stored in the provider so we can use it and the functions inside. The context is maintained inside the states in the provider.
    const providerValue = useContext(MyContext)
    // OR
    const { firstName, setFirstName, lastName, setLastName } = useContext(MyContext)
    return ( 
        <div>
            // Provide the first child component with the values and functions from the context
            <MyChildComponentOne someProp={providerValue} />
            // Provide the second child component with the values and functions from the context
            <MyChildComponentTwo someProp={providerValue} />
            // Provide the second third component with the values and functions from the context
            <MyChildComponentThree someProp={providerValue} />
        <div/>
    )
}

Now, we still won't be able to use the context without a provider, which MUST wrap around the component(s) calling useContext for that specific context. Assuming the ParentComponent is used inside of App.jsx:

App.jsx

...imports and whatever other code you have in here

   /// The jsx for the App component or whatever component calls MyParentComponent
   return <div>
              <MyProvider>
                  <MyParentComponent>
              </MyProvider>
          </div>

To re-iterate, you are taking the state OUT of the child/parent components, and putting them INTO the Provider created by the Context. Child components of the Provider can call useContext and gain access to the data and functions in the Provider's value prop.

There is no direct way to pass information from the child to the parent, only the other way around.

But that means you can also pass functions from the parent to the child! One common pattern for achieving what you need would be to pass the state set function to the child, so it can alter the parent's state. Like so:

const Parent = (props) => {
  const [childStates, setChildStates] = useState({ child1: '', child2: '',  child3: '' })

  return (
  <div>
      <Child 
        state={childStates.child1} 
        setState={(val) => setChildStates((prev) => ({ ...prev, child1: val }))}
      />
      <Child 
        state={childStates.child2} 
        setState={(val) => setChildStates((prev) => ({ ...prev, child2: val }))}
      />
      <Child 
        state={childStates.child3} 
        setState={(val) => setChildStates((prev) => ({...prev, child3: val }))}
      />
  </div>
  );
}
Related