'Why React state in localStorage is being deleted
I'm having problems to persist the state in the local storage. It's a simple todo app. After adding one todo and refreshing it, all todos are being deleted. Could not figure out why. Here is the relevant code:
const [ todos, setTodos ] = useState([])
useEffect(() => {
│ │ console.log('b1:',JSON.parse(localStorage.todos))
│ │ if (localStorage.todos !==null) {
│ │ │ console.log(JSON.parse(localStorage.todos))
│ │ │ setTodos(JSON.parse(localStorage.todos))
│ │ }
│ │ console.log('a1:',JSON.parse(localStorage.todos))
│ }, [])
│ useEffect(() => {
│ │ // if (todos.length > 0) {
│ │ │ console.log('b2:',JSON.parse(localStorage.todos))
│ │ │ localStorage.setItem("todos", JSON.stringify(todos))
│ │ │ console.log('a2:',JSON.parse(localStorage.todos))
│ │ // }
│ }, [todos])
console output (b1 before1, a1 after1 etc):
[Log] b1: – [{text: "one", done: false, id: "6c570584b1a"}] (1)
[Log] [{text: "one", done: false, id: "6c570584b1a"}] (1)
[Log] a1: – [{text: "one", done: false, id: "6c570584b1a"}] (1)
[Log] b2: – [{text: "one", done: false, id: "6c570584b1a"}] (1)
[Log] a2: – [] (0)
[Log] b1: – [] (0)
[Log] [] (0)
[Log] a1: – [] (0)
[Log] b2: – [] (0)
[Log] a2: – [] (0)
[Log] b2: – [] (0)
[Log] a2: – [] (0)
Solution 1:[1]
useState
hook is asynchronous. According to your code, you call setTodos
in the 1st useEffect
and then call todos
state in the 2nd useEffect
that is not sure todos
state gets updated.
useEffect
will be also triggered for the first time after the component rendering even though it has dependencies (in your case, it's [todos]
).
For a possible fix, you should add a condition to check todos
state before updating it again in the 2nd useEffect
.
useEffect(() => {
console.log('b1:',JSON.parse(localStorage.todos))
if (localStorage.todos !==null) {
console.log(JSON.parse(localStorage.todos))
setTodos(JSON.parse(localStorage.todos))
}
console.log('a1:',JSON.parse(localStorage.todos))
}, [])
useEffect(() => {
if (todos && todos.length > 0) {
console.log('b2:',JSON.parse(localStorage.todos))
localStorage.setItem("todos", JSON.stringify(todos))
console.log('a2:',JSON.parse(localStorage.todos))
}
}, [todos])
Those side-effects you implemented are not suitable for delete-all case. So I'd propose 2 solutions
The 1st solution is using another state to track the first load and next loads (like calling APIs)
const [isLoaded, setIsLoaded] = useState(false)
useEffect(() => {
console.log('b1:',JSON.parse(localStorage.todos))
if (localStorage.todos !==null) {
console.log(JSON.parse(localStorage.todos))
setTodos(JSON.parse(localStorage.todos))
setIsLoaded(true)
}
console.log('a1:',JSON.parse(localStorage.todos))
}, [])
useEffect(() => {
if(isLoaded) {
console.log('b2:',JSON.parse(localStorage.todos))
localStorage.setItem("todos", JSON.stringify(todos))
console.log('a2:',JSON.parse(localStorage.todos))
}
}, [todos, isLoaded])
The 2nd solution is updating localStorage
in deleteAll
instead
useEffect(() => {
console.log('b1:',JSON.parse(localStorage.todos))
if (localStorage.todos !==null) {
console.log(JSON.parse(localStorage.todos))
setTodos(JSON.parse(localStorage.todos))
}
console.log('a1:',JSON.parse(localStorage.todos))
}, [])
useEffect(() => {
if (todos && todos.length > 0) {
console.log('b2:',JSON.parse(localStorage.todos))
localStorage.setItem("todos", JSON.stringify(todos))
console.log('a2:',JSON.parse(localStorage.todos))
}
}, [todos])
function deleteAll() {
setTodos(null)
localStorage.removeItem("todos")
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |