skip to Main Content

Whenever a new item is added to the list, the local storage is updated but if i refresh the page. The key remains but the value is reset to an empty array.

import { useState, useEffect } from 'react';

function App() {
  const [data, setData] = useState([]);
  const [input, setInput] = useState('');

  useEffect(() => {
    const storedData = localStorage.getItem('react-data-store');
    if (storedData) {
        setData(JSON.parse(storedData));
    }
  }, []);

  useEffect(() => {
    localStorage.setItem('react-data-store', JSON.stringify(data));
  }, [data]);

  const handleAddItem = () => {
    if (input) {
      const newItem = { id: crypto.randomUUID(), text: input };
      setData([...data, newItem]);
      setInput('');
    }
  };

  return (
    <>
      <input
        type='text'
        value={input}
        onChange={(e) => setInput(e.target.value)}
      />
      <button onClick={handleAddItem}>Add Item</button>
      <ul>
        {data.map((item) => (
          <li key={item.id}>{item.text}</li>
        ))}
      </ul>
    </>
  );
}

export default App;

I was expecting the list to remain even after a refresh but the data does not persist.

4

Answers


  1. These two useEffect hooks conflict each other

     useEffect(() => {
       const storedData = localStorage.getItem('react-data-store');
       if (storedData) {
         setData(JSON.parse(storedData));
       }
     }, []);
    
     useEffect(() => {
       localStorage.setItem('react-data-store', JSON.stringify(data));
     }, [data]);
    

    My solution is to create a new flag that check whether the data is loaded from localstorage or not

     const [isInit, setIsInit] = useState(false);
    
     useEffect(() => {
       const storedData = localStorage.getItem('react-data-store');
       if (storedData) {
         setData(JSON.parse(storedData));
       }
       setIsInit(true)
     }, []);
    
     useEffect(() => {
       if (isInit)
         localStorage.setItem('react-data-store', JSON.stringify(data));
     }, [data, isInit]);
    
    Login or Signup to reply.
  2. localStorage value resets after every refresh

    setData is asynchronous. The localStorage.setItem not. The function from the second useEffect will be called before setData from the first useEffect causing your localStorage to be overwritten with empty array, because in that moment data is [].

    useEffect(() => {
        const storedData = localStorage.getItem('react-data-store');
        if (storedData) {
           setData(JSON.parse(storedData)); // will be called after second useEffect
        }
    }, []);
    

    Since localStorage.setItem is synchronous, I would suggest you to remove the first useEffect completely and move the logic directly to the useState hook to make it a default value.

    const [data, setData] = useState(() => {
       const storedData = localStorage.getItem('react-data-store');
       if (storedData) {
         return JSON.parse(storedData);
       }
    
       return [];
    });
    
    Login or Signup to reply.
  3. check if the data variable has data in it, because the useEffect that watches data is called on the first render.

    import { useState, useEffect } from 'react';
    
    function App() {
      const [data, setData] = useState([]);
      const [input, setInput] = useState('');
    
      useEffect(() => {
        const storedData = localStorage.getItem('react-data-store');
        if (storedData) {
          setData(JSON.parse(storedData));
        }
      }, []);
    
      useEffect(() => {
        if (data.length > 0) // <----- here
          localStorage.setItem('react-data-store', JSON.stringify(data));
      }, [data]);
    
      const handleAddItem = () => {
        if (input) {
          const newItem = { id: crypto.randomUUID(), text: input };
          setData([...data, newItem]);
          setInput('');
        }
      };
    
      return (
        <>
          <input
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
          />
          <button onClick={handleAddItem}>Add Item</button>
          <ul>
            {data.map((item) => (
              <li key={item.id}>{item.text}</li>
            ))}
          </ul>
        </>
      );
    }
    
    export default App;
    

    better approach :

    stakblitz example : https://stackblitz.com/edit/react-bhdhmy?file=src%2FApp.js

    Removed Second useEffect: The second useEffect which was updating the local storage whenever data changed has been removed.

    Added handleSetData Function: Introduced a new function called handleSetData that both updates the data state and saves this new data directly to local storage. This makes sure local storage stays in sync with state updates without the need for a useEffect.

    Modified handleAddItem Function: Refactored the handleAddItem function to use the new handleSetData function, ensuring that when a new item is added, it is both added to the state and saved to local storage.

    Added handleDeleteAll Function: Introduced a new function to clear all items. This function sets the data state to an empty array and also updates the local storage to reflect this change.

    UI Additions: Added a new button to the component’s return JSX that invokes handleDeleteAll when clicked, allowing users to clear all items.

     import React, { useState, useEffect } from 'react';
    
    function App() {
      const [data, setData] = useState([]);
      const [input, setInput] = useState('');
    
      useEffect(() => {
        const storedData = localStorage.getItem('react-data-store');
        if (storedData) {
          setData(JSON.parse(storedData));
        }
      }, []);
    
      //delete useEffect
    
      const handleSetData = (data) => {
        setData(() => data);
        localStorage.setItem('react-data-store', JSON.stringify(data));
      };
      const handleAddItem = () => {
        if (input) {
          const newItem = { id: crypto.randomUUID(), text: input };
          handleSetData([...data, newItem]);
          setInput('');
        }
      };
      const handleDeleteAll = () => {
        handleSetData([]);
      };
      return (
        <>
          <input
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
          />
          <button onClick={handleAddItem}>Add Item</button>
          <button onClick={handleDeleteAll}>delete all</button>
          <ul>
            {data.map((item) => (
              <li key={item.id}>{item.text}</li>
            ))}
          </ul>
        </>
      );
    }
    
    export default App;
    
    Login or Signup to reply.
  4. You can remove second the useEffect part and save the data in handleAddItem function.
    Here is the full code you require.

    import { useState, useEffect } from 'react';
    
    function App() {
      const [data, setData] = useState([]);
      const [input, setInput] = useState('');
    
      useEffect(() => {
        const storedData = localStorage.getItem('react-data-store');
        if (storedData) {
            setData(JSON.parse(storedData));
        }
      }, []);
    
      const handleAddItem = () => {
        if (input) {
          const newItem = { id: crypto.randomUUID(), text: input };
          setData([...data, newItem]);
          setInput('');
          localStorage.setItem('react-data-store', JSON.stringify([...data, newItem]));
        }
      };
    
      return (
        <>
          <input
            type='text'
            value={input}
            onChange={(e) => setInput(e.target.value)}
          />
          <button onClick={handleAddItem}>Add Item</button>
          <ul>
            {data.map((item) => (
              <li key={item.id}>{item.text}</li>
            ))}
          </ul>
        </>
      );
    }
    
    export default App;
    

    That should be worked.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search