Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using React hooks, how can I update an object that is being passed to a child via props?

The parent component contains an array of objects. It maps over the array and returns a child component for every object, populating it with the info of that object. Inside each child component there is an input field that I'm hoping will allow the user to update the object, but I can't figure out how to go about doing that. Between the hooks, props, and object immutability, I'm lost conceptually. Here's a simplified version of the parent component:

const Parent = () => {
  const [categories, setCategories] = useState([]);

  useEffect(()=>{
    // makes an axios call and triggers setCategories() with the response
  }

  return(
    categories.map((element, index) => {
      return(
        <Child
          key = {index}
          id = {element.id}
          firstName = {element.firstName}
          lastName = {element.lastName}
          setCategories = {setCategories}
    })
  )
}

And here's a simplified version of the child component:

const Child = (props) => {
  return(
    <h1>{props.firstName}</h1>
    <input
      defaultValue = {props.lastName}
      onChange={()=>{
        // This is what I need help with.
        // I'm a new developer and I don't even know where to start.
        // I need this to update the object's lastName property in the parent's array.
      }}
  )
}
like image 637
squidslippers Avatar asked Oct 21 '25 04:10

squidslippers


1 Answers

Maybe without knowing it, you have lifted the state: basically, instead of having the state in the Child component, you keep it in the Parent.
This is an used pattern, and there's nothing wrong: you just miss a handle function that allows the children to update the state of the Parent: in order to do that, you need to implement a handleChange on Parent component, and then pass it as props to every Child.

Take a look at this code example:

const Parent = () => {
    const [categories, setCategories] = useState([]);

    useEffect(() => {
        // Making your AXIOS request.
    }, []);

    const handleChange = (index, property, value) => {
        const newCategories = [...categories];
        newCategories[index][property] = value;

        setCategories(newCategories);
    }

    return categories.map((c, i) => {
        return (
            <Child
                key={i}
                categoryIndex={i}
                firstName={c.firstName}
                lastName={c.lastName}
                handleChange={handleChange} />
        );
    });
}

const Child = (props) => {
    ...

    const onInputChange = (e) => {
        props.handleChange(props.categoryIndex, e.target.name, e.target.value);
    }

    return (
        ...
        <input name={'firstName'} value={props.firstName} onChange={onInputChange} />
        <input name={'lastName'} value={props.lastName} onChange={onInputChange} />
    );
}

Few things you may not know:

  • By using the attribute name for the input, you can use just one handler function for all the input elements. Inside the function, in this case onInputChange, you can retrieve that information using e.target.name;
  • Notice that I've added an empty array dependecies in your useEffect: without it, the useEffect would have run at EVERY render. I don't think that is what you would like to have.
    Instead, I guest you wanted to perform the request only when the component was mount, and that is achievable with n empty array dependecies;
like image 122
Jolly Avatar answered Oct 23 '25 20:10

Jolly