Home > OS >  Cannot update parent's state from child component
Cannot update parent's state from child component

Time:01-25

I have a parent component which contains data to build the rows of a table. A child component renders the actual table. Every row should be deletable, so I created a function inside the parent component to update its state and I passed it to the child component, so it could be called on the click of a button.

Even though the setter function is fired the state is not actually changed. The table is not re-rendered and the useEffect which has files as a dependency is not fired.

I'm not understanding why this happens, here's the problem reproduced in codesandbox. I would be very glad if anyone could help solving this.

Edit: I'm adding the code here since links can break over time, as @UmerAbbas pointed out.

// App.js
import { useEffect, useState } from "react";
import Table from "./Table";

export default function App() {
  const [files, setFiles] = useState({
    "mario.jpg": {
      name: "mario.jpg",
      size: 1234
    },
    "luigi.mkv": {
      name: "luigi.mkv",
      size: 1234567
    }
  });
  const [rows, setRows] = useState([]);

  useEffect(() => {
    console.log("files have changed");
    setRows([]);
    const filesKeys = Object.keys(files);
    for (const filename of filesKeys) {
      setRows((prevState) => [
        ...prevState,
        { filename, size: files[filename].size }
      ]);
    }
  }, [files]);

  const handleDelete = (file) => {
    console.log(file);
    const _files = files;
    delete _files[file];
    console.log("Setting files");
    setFiles(_files);
    console.log("files set");
  };

  return (
    <div className="App">
      <Table rows={rows} handleDelete={handleDelete} />
    </div>
  );
}

// Table.jsx
export default function Table({ rows, handleDelete }) {
  return (
    <table style={{ width: "100%" }}>
      <tr>
        <th>Name</th>
        <th>Size</th>
        <th></th>
      </tr>
      {rows.map((row) => {
        return (
          <tr key={row.filename}>
            <td>{row.filename}</td>
            <td>{row.size}</td>
            <td>
              <button onClick={() => handleDelete(row.filename)}>Delete</button>
            </td>
          </tr>
        );
      })}
    </table>
  );
}

CodePudding user response:

Your issue is actually less severe than you think. In your sandbox, it seems you are attempting to update state directly by not creating a copy of files at line 32. Your sandbox works if you do create a shallow copy before making edits to the object:

const _files = { ...files };

For more information, please see here (note that though the documentation applies to class-based components the principle also extends to functional components).

CodePudding user response:

You need to use the function version of set state.

const handleDelete = (file) => {
  setFiles((prev) => {
    const { [file]: fileName, ...rest } = prev;
    return rest;
  });
  console.log("files set");
};

CodePudding user response:

Try duplicating an element like this:

  const handleDelete = (file) => {
    const _files = { ...files };  // like this
    delete _files[file];
    setFiles(_files);
  };

sandbox link

  •  Tags:  
  • Related