reactjsreact-hooksnext.jslocal-storage

Why local storage does not work with useEffect?


I have the following code and it is working fine. <AddContact /> is a simple component that presents a input form which collects name + email from user - I have attached its code at the end for completeness. The collected contacts array is stored in localStorage, and when I refresh the page, they simply get reloaded. all good

import './App.css'
import { useState, useEffect } from 'react'

function App() {
    const LOCAL_STORAGE_KEY = 'contacts'

    const [contacts, setContacts] = useState(JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) || [])

    const addNewContact = (newContact) => {
        setContacts([...contacts, newContact])
    }

    useEffect(() => {
        console.table(contacts)
        localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
    }, [contacts])

    return (
        <AddContact newContact={addNewContact} />
    )
}

export default App

my question is that the following revision does not work - every time the page is refreshed, local storage is wiped out. But it really look like it should work - I was following an online tutorial and it was working when the instructor did it.

import './App.css'
import { useState, useEffect } from 'react'

function App() {
    const LOCAL_STORAGE_KEY = 'contacts'

    const [contacts, setContacts] = useState([]) // changed line

    const addNewContact = (newContact) => {
        setContacts([...contacts, newContact])
    }

    useEffect(() => {
        console.table(contacts)
        localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
    }, [contacts])

    // added
    useEffect(() => {
        const savedContacts = JSON.parse(
            localStorage.getItem(LOCAL_STORAGE_KEY)
        )
        if (savedContacts) {
            setContacts(savedContacts)
        }
    }, [])


    return (
        <AddContact newContact={addNewContact} />
    )
}

export default App

for completeness, here's the code for <AppContact />

import React, { Component } from 'react'

export class AddContact extends Component {
    state = {
        name: '',
        email: '',
    }

    updateState = (e) => {
        this.setState({ [e.target.name]: e.target.value })
    }

    addContact = (e) => {
        e.preventDefault()
        if (this.state.name === '' || this.state.email === '') {
            return
        }
        this.props.newContact(this.state)
    }

    render() {
        return (
            <div className='ui main'>
                <h2>Add Contact</h2>
                <form className='ui form' onSubmit={this.addContact}>
                    <div className='field'>
                        <label>Name</label>
                        <input
                            type='text'
                            name='name'
                            value={this.state.name}
                            placeholder='Name'
                            onChange={this.updateState}
                        />
                    </div>
                    <div className='field'>
                        <label>Email</label>
                        <input
                            type='text'
                            name='email'
                            value={this.state.email}
                            placeholder='Email'
                            onChange={this.updateState}
                        />
                    </div>
                    <button className='ui button blue' type='submit'>
                        Add
                    </button>
                </form>
            </div>
        )
    }
}

export default AddContact

I would like to understand why the second method does not work.


Solution

  • The value you set when calling addNewContact does get stored in localStorage when the first useEffect runs (as expected). The problem is that, when you reload the page, that same useEffect is overwriting what's in localStorage because the state is reset to an empty array ([]). This triggers the useEffect with contacts equals to [], and stores that in localStorage.


    There are a few ways to handle it, but one way is to check if contacts is an empty array before storing its value to localStorage.

    useEffect(() => {
        console.table(contacts)
        if (contacts.length > 0) {
            localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
        }
    }, [contacts])
    

    This prevents the initial state of contacts to be stored in localStorage when the page gets first loaded.