Reputation: 125
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.
Upvotes: 2
Views: 5204
Reputation: 1
I faced the same issue.
I replaced
useEffect(() => {
console.table(contacts)
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
}, [contacts]);
with
useEffect(() => {
if (contacts.length > 0) {
console.table(contacts);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}
}, [contacts]);
This worked for me
Upvotes: 0
Reputation: 1
It is happening because you are reloading and assigning [] to contacts, simply add if condition for empty contacts.
useEffect(()=>{
if (contacts.length > 0) {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
} },[contacts]);
Upvotes: 0
Reputation: 50268
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.
Upvotes: 7
Reputation: 370689
In a given render, effect callbacks will run in the order that they're declared. With this:
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)
}
}, [])
On mount, the first one runs before the second - you call localStorage.setItem
before the second one runs localStorage.getItem
- so by the time the second one runs, storage has been set to the initial value of the contacts
state, which is the empty array.
To fix it, reverse their order, so that the one that calls .getItem
runs first.
useEffect(() => {
const savedContacts = JSON.parse(
localStorage.getItem(LOCAL_STORAGE_KEY)
)
if (savedContacts) {
setContacts(savedContacts)
}
}, []);
useEffect(() => {
console.table(contacts)
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
}, [contacts]);
That said, your first approach of
const [contacts, setContacts] = useState(JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) || [])
looks a lot nicer than an effect hook with an empty dependency array, IMO.
Upvotes: 1