I am doing a simple todo list to start learning React. I have the following file.
The issue I’m running into is during the form submission callback handleSubmit
. The first time I enter a new todo, lets say with the value hello
, todos
is updated and the view redraws, however, localStorage isn’t updated and the input isnt cleared by the call to setTodoEntry
.
On a subsequent form submission, say with the value world
, todos
is updated and the view redraws, however this time localStorage is updated with the value from the previous call (todoEntry === 'hello'
). The input still isnt cleared by setTodoEntry
. On subsequent form submissions localStorage is always one "step" behind, and the input never clears.
import { useState } from 'react'
import './App.css'
import Todo from './components/todo'
import { TextField, Button } from '@mui/material';
import { nanoid } from 'nanoid';
function App() {
let myStoredTodos = localStorage.getItem('todos');
const [todos, setTodos] = useState(myStoredTodos ? JSON.parse(myStoredTodos) : []);
const [todoEntry, setTodoEntry] = useState('');
function handleTodoEntry(event) {
setTodoEntry(event.target.value);
}
function handleSubmit(event) {
event.preventDefault();
if (!todoEntry) return;
setTodos([ { id: nanoid(), checked: false, value: todoEntry }, ...todos ])
localStorage.setItem('todos', JSON.stringify(todos));
setTodoEntry('');
}
return (
<>
<h1>{`//TODO`}</h1>
<form onSubmit={handleSubmit}>
<TextField variant="standard" onChange={(event) => handleTodoEntry(event)} />
<Button id="add-button" type="submit" variant="contained" disabled={!todoEntry.length}>Add</Button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<Todo checked={todo.checked} value={todo.value} />
</li>
))}
</ul>
</>
)
}
export default App;
2
Answers
setTodos will schedule the update but doesn’t immediately apply it. When you call
localStorage.setItem('todos', JSON.stringify(todos));
after setTodos, the todos array hasn’t been updated yet.Use the previous state in the SetTodos function:
If the input is not cleared, make sure you bind the todoEntry state to the TextField’s value:
<TextField variant="standard" value={todoEntry} onChange={(event) => handleTodoEntry(event)} />
React state updates are asynchronous and the value of
todos
is essentially fixed inside the closure. You can instead get the new value after the next render inside an effect.Also, to keep the
TextField
controlled, set its value totodoEntry
. (See the Uncontrolled vs Controlled section of the MUI Text Field documentation.)