How to make data persist on refresh React JS?

Viewed 2467

I have a code where I mount a table with some firebase data but for some reason the values disappear and I been struggling for the next 2 weeks trying to solve this issue I haven't found a solution to this and I have asked twice already and I have try everything so far but it keeps disappearing.

Important Update I just want to clarify the following apparently I was wrong the issue wasn't because it was a nested collection as someone mentioned in another question. The issue is because my "user" is getting lost in the process when I refresh.

I bring the user from the login to the app like this:

<Estudiantes user={user} />

and then I receive it as a props function ListadoPedidos({user})

but is getting lost and because is getting lost when I try to use my firebase as:

estudiantesRef = db.collection("usuarios").doc(user.uid).collection("estudiantes")

since the user is "lost" then the uid will be null. Since is null it will never reach the collection and the docs.

3 Answers

I suspect that it's because this useEffect does not have a dependency array and is bring run on every render.

useEffect (() => {
   window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes))
 })

Try adding a dependency array as follows:

useEffect (() => {
if (estudiantes && estudiantes.length>0)
   window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes))
 },[estudiantes])

This will still set the localStorage to [] when it runs on the first render. But when the data is fetched and estudiantes is set, the localStorage value will be updated. So I've added a check to check if it's not the empty array.

Change the dependency array of this useEffect to []:

  estudiantesRef.onSnapshot(snapshot => {
    const tempData = [];
    snapshot.forEach((doc) => {
      const data = doc.data();
      tempData.push(data);
    });
    setEstudiantes(tempData);
    console.log(estudiantes)
  })
 }, []);

I have a simple solution for you. Simply raise the parsing of localStorage up one level, passing the preloadedState into your component as a prop, and then using that to initialize your state variable.

const ListadoEstudiantes = (props) => {
  const estData = JSON.parse(window.localStorage.getItem('estudiantes'));
  return <Listado preloadedState={estData} {...props} />;
};

Then initialize state with the prop

  const initialState = props.preloadedState || [];
  const [estudiantesData, setEstudiantesData] = useState(initialState);

And finally, update the useEffect hook to persist state any time it changes.

  useEffect(() => {
    window.localStorage.setItem('estudiantes', JSON.stringify(estudiantes));
  }, [estudiantes]);

Full Code

import React, { useState, useEffect } from 'react';
import { db } from './firebase';
import { useHistory } from 'react-router-dom';
import './ListadoEstudiantes.css';
import {
  DataGrid,
  GridToolbarContainer,
  GridToolbarFilterButton,
  GridToolbarDensitySelector,
} from '@mui/x-data-grid';
import { Button, Container } from '@material-ui/core';
import { IconButton } from '@mui/material';
import PersonAddIcon from '@mui/icons-material/PersonAddSharp';
import ShoppingCartSharpIcon from '@mui/icons-material/ShoppingCartSharp';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import { Box } from '@mui/system';

const ListadoEstudiantes = (props) => {
  const estData = JSON.parse(window.localStorage.getItem('estudiantes'));
  return <Listado preloadedState={estData} {...props} />;
};

const Listado = ({ user, preloadedState }) => {
  const history = useHistory('');
  const crearEstudiante = () => {
    history.push('/Crear_Estudiante');
  };

  const initialState = preloadedState || [];
  const [estudiantesData, setEstudiantesData] = useState(initialState);

  const parseData = {
    pathname: '/Crear_Pedidos',
    data: estudiantesData,
  };

  const realizarPedidos = () => {
    if (estudiantesData == 0) {
      window.alert('Seleccione al menos un estudiante');
    } else {
      history.push(parseData);
    }
  };

  function CustomToolbar() {
    return (
      <GridToolbarContainer>
        <GridToolbarFilterButton />
        <GridToolbarDensitySelector />
      </GridToolbarContainer>
    );
  }

  const [estudiantes, setEstudiantes] = useState([]);
  const [selectionModel, setSelectionModel] = useState([]);
  const columns = [
    { field: 'id', headerName: 'ID', width: 100 },

    { field: 'nombre', headerName: 'Nombre', width: 200 },

    { field: 'colegio', headerName: 'Colegio', width: 250 },

    { field: 'grado', headerName: 'Grado', width: 150 },
    {
      field: 'delete',
      width: 75,
      sortable: false,
      disableColumnMenu: true,
      renderHeader: () => {
        return (
          <IconButton
            onClick={() => {
              const selectedIDs = new Set(selectionModel);
              estudiantes
                .filter((x) => selectedIDs.has(x.id))
                .map((x) => {
                  db.collection('usuarios')
                    .doc(user.uid)
                    .collection('estudiantes')
                    .doc(x.uid)
                    .delete();
                });
            }}
          >
            <DeleteOutlinedIcon />
          </IconButton>
        );
      },
    },
  ];

  const deleteProduct = (estudiante) => {
    if (window.confirm('Quiere borrar este estudiante ?')) {
      db.collection('usuarios').doc(user.uid).collection('estudiantes').doc(estudiante).delete();
    }
  };

  useEffect(() => {}, [estudiantesData]);

  const estudiantesRef = db.collection('usuarios').doc(user.uid).collection('estudiantes');
  useEffect(() => {
    estudiantesRef.onSnapshot((snapshot) => {
      const tempData = [];
      snapshot.forEach((doc) => {
        const data = doc.data();
        tempData.push(data);
      });
      setEstudiantes(tempData);
      console.log(estudiantes);
    });
  }, []);

  useEffect(() => {
    window.localStorage.setItem('estudiantes', JSON.stringify(estudiantes));
  }, [estudiantes]);

  return (
    <Container fixed>
      <Box mb={5} pt={2} sx={{ textAlign: 'center' }}>
        <Button
          startIcon={<PersonAddIcon />}
          variant="contained"
          color="primary"
          size="medium"
          onClick={crearEstudiante}
        >
          Crear Estudiantes
        </Button>
        <Box pl={25} pt={2} mb={2} sx={{ height: '390px', width: '850px', textAlign: 'center' }}>
          <DataGrid
            rows={estudiantes}
            columns={columns}
            pageSize={5}
            rowsPerPageOptions={[5]}
            components={{
              Toolbar: CustomToolbar,
            }}
            checkboxSelection
            //Store Data from the row in another variable
            onSelectionModelChange={(id) => {
              setSelectionModel(id);
              const selectedIDs = new Set(id);
              const selectedRowData = estudiantes.filter((row) => selectedIDs.has(row.id));
              setEstudiantesData(selectedRowData);
            }}
            {...estudiantes}
          />
        </Box>
        <Button
          startIcon={<ShoppingCartSharpIcon />}
          variant="contained"
          color="primary"
          size="medium"
          onClick={realizarPedidos}
        >
          Crear pedido
        </Button>
      </Box>
    </Container>
  );
};

  • The data flow in your code is somewhat contradictory, so I modify your code, and it works fine.
  • You can also try delete or add button, it will modify firebase collection, then update local data.
  • You can click refresh button in codesandbox previewer (not browser) to observe the status of data update.

Here is the code fargment :

  // Set value of `localStorage` to component state if it exist.
  useEffect(() => {
    const localStorageEstData = window.localStorage.getItem("estudiantes");
    localStorageEstData && setEstudiantes(JSON.parse(localStorageEstData));
  }, []);

  // Sync remote data from firebase to local component data state.
  useEffect(() => {
    // Subscribe onSnapshot
    const unSubscribe = onSnapshot(
      collection(db, "usuarios", user.id, "estudiantes"),
      (snapshot) => {
        const remoteDataSource = snapshot.docs.map((doc) => ({
          id: doc.id,
          ...doc.data()
        }));
        console.info(remoteDataSource);
        setEstudiantes(remoteDataSource);
      }
    );
    return () => {
      //unSubscribe when component unmount.
      unSubscribe();
    };
  }, [user.id]);

  // when `estudiantes` state update, `localStorage` will update too.
  useEffect(() => {
    window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes));
  }, [estudiantes]);

Here is the full code sample :

Edit TextField selectionStart

Hope to help you :)

Related