Search code examples
javascriptreactjsreact-hookslocal-storageuse-effect

localStorage getting updated with initial state value on refresh


I am trying to use localStorage to persist the component's state on refresh. The component works fine on initial render of the page, but the component fails with the message saying: TypeError: items.map is not a function

TestLocalStorage
src/TestLocalStorage.jsx:54

Here's the code:

import React, { useEffect, useState } from 'react';

function TestLocalStorage() {
    const [items, setItems] = useState([]);

    useEffect(() => {
        const storedData = localStorage.getItem("items");
        if(!storedData) {
            Axios.get('/url').then(response => {
                const serviceResponse = response.data.hits.map(obj=> ({ ...obj, voteCount: 0 
           }));
                localStorage.setItem("items", JSON.stringify(serviceResponse));
                setItems(serviceResponse);
            });
        } else {
            setItems(storedData);
        }
    }, []);

    useEffect(() => {
        localStorage.setItem("items", JSON.stringify(items));
    })

    function increment(id) {
        const elementsIndex = items.findIndex(element => element.objectID === id )
        items[elementsIndex].voteCount++;
        setItems([...items]);
    }

    return (
        <table cellPadding="0" cellSpacing="0" width="100%" background-color="#f6f6ef">
         <tr>
            <td>
               <table border="0" cellPadding="0" cellSpacing="0" width="100%">
                    <tr bgcolor="#ff6600" style={{ color:"white", fontWeight: "bold" }}>
                        <td> Comments </td>
                        <td> Vote Count</td>
                        <td> vote</td>
                    </tr>
                    {
                        items.map((item) => {
                            return (
                            <tr className="table-rows" key={item.objectID}>
                                <td>{ item.comments }</td>
                                <td>{ item.voteCount }</td>
                                <td>
                                    <div 
                                        style={{ 
                                            width: 0, 
                                            height: 0, 
                                            borderTop: 0, 
                                            borderLeft: "5px solid transparent", 
                                            borderRight: "5px solid transparent", 
                                            borderBottom: "10px solid grey" 
                                        }}
                                        onClick={() => increment(item.objectID)} 
                                    />
                                </td>
                                <td>{ item.title }</td>
                            </tr>)
                        })
                    }
               </table>
            </td>
         </tr>
      </table>
    );
}
export default TestLocalStorage;

But I noticed that on second render the localStorage has initial value [] set instead of the newly updated one. Not sure where I am going wrong. Please help


Solution

  • Do the parsing on the string which you are extracting from local storage by using JSON.parse()

    function TestLocalStorage() {
    
            const [items, setItems] = useState([]);
        
            useEffect(() => {
                const storedData = localStorage.getItem("items");
                if(!storedData) {
                    Axios.get('/url').then(response => {
                        const serviceResponse = response.data.hits.map(obj=> ({ ...obj, voteCount: 0 
                   }));
                        localStorage.setItem("items", JSON.stringify(serviceResponse));
                        setItems(serviceResponse);
                    });
                } else {
                    setItems(storedData);
                }
            }, []);
    
            ...............
            ...............
            ...............
    
    }
    

    Modify your function like below. Also, do a check for items value before doing map operation on the array to prevent uncaught exceptions

    function TestLocalStorage() {
    
            const [items, setItems] = useState([]);
        
            useEffect(() => {
                const storedData = JSON.parse(localStorage.getItem("items"));
                if(!storedData) {
                    Axios.get('/url').then(response => {
                        const serviceResponse = response.data.hits.map(obj=> ({ ...obj, voteCount: 0 
                   }));
                        localStorage.setItem("items", JSON.stringify(serviceResponse));
                        setItems(serviceResponse);
                    });
                } else {
                    setItems(storedData);
                }
            }, []);
    
            ...............
            ...............
            ...............
            return (
               .....
               .....
               items && items.map((item) => {
               .....
               .....
            )
    }