Home > Blockchain >  React - Resetting children state when parent changes its state in functional components
React - Resetting children state when parent changes its state in functional components

Time:10-30

I'm working with a list of notes in React Native, and I was using a bad-performant method to select/deselect the notes when I'm "edit mode". Everytime I selected a note, the application had to re-render the entire list everytime. If I do a test with 100 notes, I get input lags when I select/deselect a note, obviously.

So I decided to move the "select state" to the single Child component. By doing this, I'm having the re-render only on that component, so it's a huge improvement of performance. Until here, everything's normal.

The problem is when I'm disabling edit mode. If I select, for example, 3 notes, and I disable the "edit mode", those notes will remain selected (indeed also the style will persist). I'd like to reset the state of all the selected note, or finding a valid alternative.

I recreated the scene using React (not React Native) on CodeSandbox with a Parent and a Child: https://codesandbox.io/s/loving-field-bh0k9k

The behavior is exactly the same. I hope you can help me out. Thanks.

tl;dr:

Use-case:

  1. Go in Edit Mode by selecting a note for .5s
  2. Select 2/3 elements by clicking on them
  3. Disable Edit Mode by selecting a note for .5s

Expectation: all elements get deselected (state of children resetted)

Reality: elements don't get deselected (state of children remains the same)

CodePudding user response:

this is easy enough to do with a useEffect hook. It allows you to "watch" variable changes over time.

When editMode changes the contents of the Effect hook runs, so when editMode goes from true to false, it will set the item's selected state.

Add this to your <Child /> component:

  useEffect(() => {
    if (!editMode) {
      setSelected(false);
    }
  }, [editMode]);

CodePudding user response:

If you use React.memo you can cache the Child components and prevent their re-renders.

const Parent = () => {
  const [editMode, setEditMode] = useState(false);
  const [childrenList, setChildrenList] = useState(INITIAL_LIST);
  const [selected, setSelected] = useState([]);

  const toggleEditMode = useCallback(() => {
    if (editMode) {
      setSelected([]);
    }
    setEditMode(!editMode);
  }, [editMode]);

  const deleteSelectedChildren = () => {
    setChildrenList(childrenList.filter((x) => !selected.includes(x.id)));
    setEditMode(false);
  };

  const onSelect = useCallback((id) => {
    setSelected((prev) => {
      if (prev.includes(id)) {
        return prev.filter((x) => x !== id);
      }
      return [...prev, id];
    });
  }, []);

  // Check when <Parent /> is re-rendered
  console.log("Parent");

  return (
    <>
      <h1>Long press an element to enable "Edit Mode"</h1>
      <ul className="childrenWrapper">
        {childrenList.map((content, index) => (
          <Child
            key={content.id}
            index={index}
            content={content}
            editMode={editMode}
            toggleEditMode={toggleEditMode}
            onSelect={onSelect}
            selected={selected.includes(content.id)}
          />
        ))}
      </ul>
      {editMode && (
        <button onClick={deleteSelectedChildren}>DELETE SELECTED</button>
      )}
    </>
  );
};

You have to wrap the functions you pass as props inside useCallback, otherwise they will be different on every Parent render, invalidating the memoization.

import { useRef, memo } from "react";

const Child = memo(
  ({ content, editMode, toggleEditMode, onSelect, selected }) => {
    // Prevent re-rendering when setting timer thread
    const timerRef = useRef();

    // Toggle selection of the <Child /> and update selectedChildrenIndexes
    const toggleCheckbox = () => {
      if (!editMode) return;
      onSelect(content.id);
    };

    // Toggle Edit mode after .5s of holding press on a Child component
    const longPressStartHandler = () => {
      timerRef.current = setTimeout(toggleEditMode, 500);
    };

    // Release setTimeout thread in case it's pressed less than .5s
    const longPressReleaseHandler = () => {
      clearTimeout(timerRef.current);
    };

    // Check when <Child /> is re-rendered
    console.log("Child - "   content.id);

    return (
      <li
        className={`childContent ${editMode && "editMode"} ${
          selected && "selected"
        }`}
        onm ouseDown={longPressStartHandler}
        onm ouseUp={longPressReleaseHandler}
        onClick={toggleCheckbox}
      >
        <pre>
          <code>{JSON.stringify(content)}</code>
        </pre>
        {editMode && (
          <input type="checkbox" onChange={toggleCheckbox} checked={selected} />
        )}
      </li>
    );
  }
);

You can see a working example here.

  • Related