Home > Net >  React state not updating when used outside hook
React state not updating when used outside hook

Time:03-12

I'm playing around with a hook that can store some deleted values. No matter what I've tried, I can't get the state from this hook to update when I use it in a component.

const useDeleteRecords = () => {
  const [deletedRecords, setDeletedRecords] = React.useState<
    Record[]
  >([]);
  const [deletedRecordIds, setDeletedRecordIds] = React.useState<string[]>([]);
  // ^ this second state is largely useless – I could just use `.filter()` 
  // but I was experimenting to see if I could get either to work.

  React.useEffect(() => {
    console.log('records changed', deletedRecords);
    // this works correctly, the deletedRecords array has a new item 
    // in it each time the button is clicked
    setDeletedRecordIds(deletedRecords.map((record) => record.id));
  }, [deletedRecords]);

  const deleteRecord = (record: Record) => {
    console.log(`should delete record ${record.id}`); 
    // This works correctly - firing every time the button is clicked
    setDeletedRecords(prev => [...prev, record]);
  };

  const wasDeleted = (record: Record) => {
    // This never works – deletedRecordIds is always [] when I call this outside the hook
    return deletedRecordIds.some((r) => r === record.id);
  };

  return {
    deletedRecordIds,
    deleteRecord,
    wasDeleted,
  } // as const <-- no change
}

Using it in a component:

const DisplayRecord = ({ record }: { record: Record }) => {

   const { deletedRecordIds, wasDeleted, deleteRecord } = useDeleteRecords();

   const handleDelete = () => {
     // called by a button on a row
     deleteRecord(record);
   }

   React.useEffect(() => {
     console.log('should fire when deletedRecordIds changes', deletedRecordIds);
     // Only fires once for each row on load? deletedRecordIds never changes
     // I can rip out the Ids state and do it just with deletedRecords, and the same thing happens
   }, [deletedRecordIds]);

}

If it helps, these are in the same file – I'm not sure if there's some magic to exporting a hook in a dedicated module? I also tried as const in the return of the hook but no change.

Here's an MCVE of what's going on: https://codesandbox.io/s/tender-glade-px631y?file=/src/App.tsx

Here's also the simpler version of the problem where I only have one state variable. The deletedRecords state never mutates when I use the hook in the parent component: https://codesandbox.io/s/magical-newton-wnhxrw?file=/src/App.tsx

CodePudding user response:

problem

In your App (code sandbox) you call useDeleteRecords, then for each record you create a DisplayRecord component. So far so good.

function App() {
  const { wasDeleted } = useDeleteRecords(); // ✅
  console.log("wtf");
  return (
    <div className="App" style={{ width: "70vw" }}>
      {records.map((record) => {
        console.log("was deleted", wasDeleted(record));
        return !wasDeleted(record) ? (
          <div key={record.id}>
            <DisplayRecord record={record} /> // ✅
          </div>
        ) : null;
      })}
    </div>
  );
}

Then for each DisplayRecord you call useDeleteRecords. This maintains a separate state array for each component ⚠️

const DisplayRecord = ({ record }: { record: Record }) => {
  const { deletedRecords, deleteRecord } = useDeleteRecords(); // ⚠️

  const handleDelete = () => {
    // called by a button on a row
    deleteRecord(record);
  };

  React.useEffect(() => {
    console.log("should fire when deletedRecords changes", deletedRecords);
    // Only fires once for each row on load? deletedRecords never changes
  }, [deletedRecords]);

  return (
    <div>
      <div>{record.id}</div>
      <div onClick={handleDelete} style={{ cursor: "pointer" }}>
        [Del]
      </div>
    </div>
  );
};

solution

The solution is to maintain a single source of truth, keeping handleDelete and deletedRecords in the shared common ancestor, App. These can be passed down as props to the dependent components.

function App() {
  const { deletedRecords, deleteRecord, wasDeleted } = useDeleteRecords(); //            
  • Related