Home > Software engineering >  Best Practice to Implement React Parent-Child Components Using Hooks
Best Practice to Implement React Parent-Child Components Using Hooks

Time:12-24

I'm picking up React and not sure if I'm doing this correctly. To preface the question I've read all about the React hooks; I understand them in isolation but am having trouble piecing them together in a real-life scenario.

Imagine I have a Parent component housing a list of Child components generated via a map function on the parent:

<Parent>
  {items.map(i => <Child item={i} />)}
</Parent>

And say the Child component is just a simple:

function Child({item}) {
  return <div>{item}</div>
}

However the Child component needs to update its view, and be able to delete itself. My question is - should I call useState(item) on the child so it internally has a copy of the item? In that case if I updated the item the items list in the parent wouldn't get updated right? To fix that I ended up having something that looks like:

<Parent>
  {items.map(i => 
    <Child 
      item={i} 
      updateItem={(index) => setItems( /* slice and concat items list at index */ )}
      deleteItem={(index) => setItems( /* slice items list at index */ )}
    />)
  }
</Parent>

And the Child component simply invokes updateItem and deleteItem as appropriate, not using any React hooks.

My question here are as follows:

  1. should I have used useState in the child component?
  2. should I have used useCallback on the updateItem/deleteItem functions somehow? I tried using it but it didn't behave correctly (the correct item in the Parent got removed but the state in the remaining rendered Child were showing values from the deleted Child for example.
  3. My understanding is that this would be very inefficient because an update on 1 child would force all other children to re-render despite them not having been updated.

If done most properly and efficiently, what should the code look like?

Thanks for the pointers.

CodePudding user response:

No you don't have to create internal state. That's an anti pattern to create a local state just to keep a copy of props of the component.

You can keep your state on parent component in your case. Your child component can execute callbacks like you used,

for example,

const [items, _] = useState(initialItemArray);

const updateItem = useCallback((updatedItem) => {
  // do update
}, [items])

const deleteItem = useCallback((item) => {
 // do delete
}, [items])

<Child
  data={item}
  onUpdate={updateItem}
  onDelete={deleteItem}
/>

Also note you shouldn't over use useCallback & useMemo. For example, if your list is too large and you use useMemo for Child items & React re renders multiple 100 - 1000 of list items that can cause performance issue as React now have to do some extra work in memo hoc to decide if your <Child /> should re render or not. But if the Child component contain some complex UI ( images, videos & other complex UI trees ) then using memo might be a better option.


To fix the issue in your 3rd point, you can add some unique key ids for each of your child components.

<Child
  key={item.id} // assuming item.id is unique for each item
  data={item}
  onUpdate={(updatedItem) => {}}
  onDelete={(item) => {}}
/>

Now react is clever enough not to re render whole list just because you update one or delete one. This is one reason why you should not use array index as the key prop

CodePudding user response:

should I have used useState in the child component?

Usually duplicating state is not a good idea; so probably no.

should I have used useCallback on the updateItem/deleteItem functions somehow

You might need it if you want to pass those callbacks to components wrapped in React.memo.

My understanding is that this would be very inefficient because an update on 1 child would force all other children to re-render despite them not having been updated

Yes your understanding is correct, but whether you would notice the slow down, depends on number of things such as how many child components there are, what each of them renders, etc.

If done most properly and efficiently, what should the code look like?

See below. Notice I added React.memo which together with useCallback should prevent those items from re rendering, props of which didn't change.

const Child = React.memo(function MyComponent({ item, update }) {
  console.log('Rendered', item);
  return (
    <div
      onClick={() => {
        update(item);
      }}
    >
      {item.name}
    </div>
  );
});

let itemsData = [
  { id: 0, name: 'item1' },
  { id: 1, name: 'item2' },
];
export default function App() {
  let [items, setItems] = React.useState(itemsData);
  let update = React.useCallback(
    (item) =>
      setItems((ps) =>
        ps.map((x) => (x.id === item.id ? { ...x, name: 'updated' } : x))
      ),
    []
  );
  return (
    <div>
      {items.map((item) => (
        <Child key={item.id} item={item} update={update} />
      ))}
    </div>
  );
}

Now if you click item1, console.log for item2 won't be called.

  • Related