React data loss on navigation due to re-rendering

Viewed 143

I have a form component and a table component. On submitting the form, the table will be updated with the form inputs. But the problem is, If I navigate to another page and come back, I'm losing all the previously entered data and the table is empty. Where am I making a mistake? Thank you in advance.

const DocumentTable=()=> {

  const docData = [{ id: "", name: "", code: "" }];
  const [documentData, setDocumentData] = useState(docData);

  const tableRows = documentData.map((info) => {
    return (
      <Tr>
        <Td>{info.id}</Td>
        <Td>{info.name}</Td>
        <Td>{info.code}</Td>
      </Tr>
    );
  });

  const addRows = (data) => {
    const totalDocuments = documentData.length;
    data.id = totalDocuments;
    const updatedDocumentData = [...documentData];
    updatedDocumentData.push(data);
    setDocumentData(updatedDocumentData);
  };

  return (
    <Box>
      <DocumentForm func={addRows} />
      <TableContainer mt={'20'}>
        <Table variant="striped" colorScheme="teal" size='lg'>
          <Thead>
            <Tr>
              <Th>Document ID</Th>
              <Th>Document Name</Th>
              <Th>Document Code</Th>
            </Tr>
          </Thead>
          <Tbody>{tableRows}</Tbody>
        </Table>
      </TableContainer>
      {console.log(documentData)}
    </Box>
  );
}

export default DocumentTable;`
2 Answers

I am going to assume you are a novice programmer. Your code looks good for state management within this page, but states only exist within one page. They won't be saved between pages. To store data between pages there are multiple approaches you can take from here. It highly depends on your use case which one is best.

  1. If you just want to maintain this state within the application while it is used you are best off with useContext, another react hook for state management over the entire application. HOWEVER, this data will be lost on page reload or if you leave the site/application.

  2. If you want to keep this information between page reloads and people leaving the site you will have to save it into a cookie. Then it could theoretically be stored forever, but on a browser, so it will be different for all users.

  3. To really save this information you need a database which can store it. You would fetch data from this database on page load. When addRows is called they will update the data into the database. This is outside of react and a lot of work, and the database will need to be hosted which often costs money.

Neither of these are quick fixes so I can't help you any further. I hope it sends you in the right direction. Let me know if you have any questions.

Issue

React state only exists in memory and only during the life of the React component holding it. If the component unmounts for any reason, the memory holding the state is released and garbage collected. If you need to hold on to the state longer than the component life then you must lift the state higher in the ReactTree to an ancestor component (e.g. in a parent component and passed down as props, in a React Context, app state management like Redux, etc...) that lives longer, and if you must hold on to the state through page reloads then you must persist it to some longer-term storage, e.g. localStorage.

Solution

Lift state to App or components rendering routes and the DocumentTable and the other routes/components that users navigate to. I highly suggest not using the array length as any part of element ids because as elements are later removed from state the reduced array length will likely introduce a duplicate document id property. Instead, use a utility function that guarantees unique GUIDs.

Example:

App

import { v4 as uuidV4 } from 'uuid';

const docData = [{ id: "", name: "", code: "" }];

const App = () => {
  const [documentData, setDocumentData] = useState(docData);

  const addRows = (data) => {
    setDocumentData(documentData => [
      ...documentData,
      {
        ...data,
        id: uuidV4(),
      },
    ]);
  };

  ...

  return (
    ...
    <Routes>
      ... other routes ...
      <Route
        path="documents"
        element={(
          <DocumentTable
            addRows={addRows}
            documentData={documentData}
          />
        )}
      />
      ... other routes ...
    </Routes>
    ...
  );
};

DocumentTable

const DocumentTable = ({ addRows, documentData }) => {
  return (
    <Box>
      <DocumentForm func={addRows} />
      <TableContainer mt={'20'}>
        <Table variant="striped" colorScheme="teal" size='lg'>
          <Thead>
            <Tr>
              <Th>Document ID</Th>
              <Th>Document Name</Th>
              <Th>Document Code</Th>
            </Tr>
          </Thead>
          <Tbody>
            {documentData.map((info) => (
              <Tr key={info.id}>
                <Td>{info.id}</Td>
                <Td>{info.name}</Td>
                <Td>{info.code}</Td>
              </Tr>
            ))}
          </Tbody>
        </Table>
      </TableContainer>
    </Box>
  );
}

export default DocumentTable;

To persist the state through page reloads, use localStorage to initialize the state and a useEffect hook to persist to localStorage.

const docData = [{ id: "", name: "", code: "" }];

const App = () => {
  const [documentData, setDocumentData] = useState(
    JSON.parse(localStorage.getItem("documentData")) || docData
  );

  useEffect(() => {
    localStorage.setItem("documentData", JSON.stringify(documentData));
  }, [documentData]);

  ...
};
Related