Home > OS >  Prevent losing data when refreshing on a different route - react
Prevent losing data when refreshing on a different route - react

Time:09-11

I wanted to prevent losing state on page refresh while being on a different route path. Im curious why the first example does not work. From what i understand when app mounts first thing that gonna render is component itself and then useEffects run. Since i got 3 here, first fetches and saves the data to the invoiceList state and then next useEffect that run should fill localStorage key with invoiceList state data. The last one obviously retrieve the data.

The second one does fill the "invoiceData" localStorage key with an empty array. Why is this happening if the invoiceList state already have the data after the first useEffect?

The second example that i provided works. I removed second useEffect and set localStorage key in the first useEffect with response data that i get from fetch.

I also wonder if im doing everything correct here. Any feedback appreciated :)

First example (not working):

    import { ReactElement, useEffect, useState } from "react";
    import { Outlet } from "react-router-dom";
    import { Bar } from "../../components/Bar/Bar";
    import { Invoice } from "./Root.utils";
    
    type Props = {};
    
    const Root = (props: Props): ReactElement => {
      const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
    
      useEffect(() => {
        const fetchData = async () => {
          const response = await fetch("./data.json");
          const data = await response.json();
    
          setInvoiceList(data);
        };
    
        fetchData();
      }, []);
    
      useEffect(() => {
        window.localStorage.setItem("invoiceData", JSON.stringify(invoiceList));
      }, []);
    
      useEffect(() => {
        setInvoiceList(
          JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
        );
      }, []);
    
      return (
        <div>
          <Bar />
          <Outlet context={{ invoiceList }} />
        </div>
      );
    };
    
    export default Root;

Second example (working):

    import { ReactElement, useEffect, useState } from "react";
    import { Outlet } from "react-router-dom";
    import { Bar } from "../../components/Bar/Bar";
    import { Invoice } from "./Root.utils";
    
    type Props = {};
    
    const Root = (props: Props): ReactElement => {
      const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
    
      useEffect(() => {
        const fetchData = async () => {
          const response = await fetch("./data.json");
          const data = await response.json();
    
          window.localStorage.setItem("invoiceData", JSON.stringify(data));
    
          setInvoiceList(data);
        };
    
        fetchData();
      }, []);
    
      useEffect(() => {
        setInvoiceList(
          JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
        );
      }, []);
    
      return (
        <div>
          <Bar />
          <Outlet context={{ invoiceList }} />
        </div>
      );
    };
    
    export default Root;

CodePudding user response:

The first example is never storing the data into the localStorage because the fetch is an asynchronous function that and you are writing basically always the empty array into your localStorage.

The order of execution in the first example will be:

  1. fetchData called
  2. window.localStorage.setItem("invoiceData", JSON.stringify(invoiceList)); <- still empty array
  3. setInvoiceList(JSON.parse(window.localStorage.getItem("invoiceData") || "[]"));
  4. response.json() called
  5. setInvoiceList(data); called

I would also recommend to improve your code a little like that:

    import React, { useEffect, useState } from "react";
    import { Outlet } from "react-router-dom";
    import { Bar } from "../../components/Bar/Bar";
    import { Invoice } from "./Root.utils";
    
    const Root: React.FC = () => {
      const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
    
      useEffect(() => {
        setInvoiceList(
          JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
        );
        const fetchData = async () => {
          const response = await fetch("./data.json");
          const data = await response.json();
    
          window.localStorage.setItem("invoiceData", JSON.stringify(data));
    
          setInvoiceList(data);
        };
    
        fetchData();
      }, []);
    
      return (
        <div>
          <Bar />
          <Outlet context={{ invoiceList }} />
        </div>
      );
    };
    
    export default Root;

CodePudding user response:

You can use the Link component from react-router and specify to={} as an object where you specify pathname as the route to go to. Then add a variable e.g. data to hold the value you want to pass on. See the example below.

Using the <Link /> component:

<Link
  to={{
    pathname: "/page",
    state: data // your data array of objects
  }}
>

Using history.push()

this.props.history.push({
  pathname: '/page',
    state: data // your data array of objects
})

Using either of the above options you can now access data on the location object as per the below in your page component.

render() {
  const { state } = this.props.location
  return (
    // render logic here
  )
}
  • Related