Reputation: 49
I've been stuck on this all day, and I'd appreciate some help. Thanks in advance.
At first I was writing it like this, but I'd get a type error: todos.map is not a function.
function toggleState() {
setTodos(state => ({ ...state, isComplete: !state.isComplete }))
}
Finally I realized that error was because it was returning todos as an object, so I tried this:
function toggleState() {
setKeywords(state => [{ ...state, isUsed: !state.isUsed }])
}
Now I'm not getting the type error, but it's still not working as expected. Here's the state before toggleState:
[
{
"name": "State",
"value": [
{
"todo": "Learn React",
"id": "91bad41d-1561-425a-9e77-960f731d058a",
"isComplete": false
}
]
and here's state after:
[
{
"name": "State",
"value": [
{
"0": {
"todo": "Learn React",
"id": "91bad41d-1561-425a-9e77-960f731d058a",
"isComplete": false
},
"isComplete": true
}
]
Here's the rest of my code:
import React, { useState, useEffect } from 'react'
import { uuid } from 'uuidv4'
import { Form, FormGroup, Input, Button } from 'reactstrap'
function Example(props) {
const [todos, setTodos] = useState([])
// Run when component first renders
useEffect(() => {
console.log('useEffect component first rendered')
if (localStorage.getItem('todoData')) {
setTodos(JSON.parse(localStorage.getItem('todoData')))
}
}, [])
// Run when todos state changes
useEffect(() => {
console.log('useEffect todos changed')
localStorage.setItem('todoData', JSON.stringify(todos))
}, [todos])
const [formInput, setFormInput] = useState()
function handleChange(e) {
setFormInput(e.target.value)
}
function handleSubmit(e) {
e.preventDefault()
setTodos(prev => prev.concat({ todo: formInput, id: uuid(), isComplete: false }))
setFormInput('')
}
function toggleState() {
setTodos(state => [{ ...state, isComplete: !state.isComplete }])
}
return (
<div className='text-center'>
<div className='mb-2 border text-center' style={{ height: '300px', overflowY: 'scroll' }}>
{todos.map(todo => (
<p className={todo.isUsed ? 'text-success my-1' : 'text-danger my-1'} key={todo.id}>
{todo.todo}
</p>
))}
</div>
<Form onSubmit={handleSubmit}>
<FormGroup>
<Input onChange={handleChange} type='text' name='text' id='todoForm' placeholder='Enter a todo' value={formInput || ''} />
<Button>Set Todo</Button>
</FormGroup>
</Form>
<Button onClick={toggleState}>Toggle isComplete</Button>
</div>
)
}
export default Example
Upvotes: 0
Views: 1524
Reputation: 2152
So in your specific case, where you just want to toggle the isComplete
on the first item, could be achieved like this:
function toggleState() {
setTodos(([firstItem, ...remainder]) => {
return [
{
...firstItem,
isComplete: !firstItem.isComplete
},
...remainder
];
});
}
Where we use Destructuring assignment to get the FirstItem and manipulate that, and spread the reminder back into the state.
Upvotes: 1
Reputation: 1184
The method that I end up using, and I've seen other developers do, is to copy the object or state first, do modifications to it, and then set the new state with the modified state.
I also noticed you need to provide an index for the todos to be able to toggle them, so I added that functionality.
Take a look at a working example, click "Run code snippet" below.
// main.js
// IGNORE THIS BECAUSE THIS IS JUST TO USE REACT IN STACK OVERFLOW
const { useEffect, useState } = React;
// ---- CODE STARTS HERE -----
const Example = (props) => {
const [todos, setTodos] = useState([]);
const [formInput, setFormInput] = useState('');
// Run when component first renders
useEffect(() => {
/*
// Uncomment - Just doesn't work in Stack Overflow
if (localStorage && localStorage.getItem('todoData')) {
setTodos(JSON.parse(localStorage.getItem('todoData')));
}
*/
}, []);
// Hooks
const handleChange = event => {
setFormInput(event.target.value);
};
const handleSubmit = event => {
const newTodosState = [...todos ]; // make copy
newTodosState.push({ todo: formInput, isComplete: false });
setTodos(newTodosState);
// Add functionality to update localStorage
// ex:
// localStorage.setItem('todoData', newTodosState);
// Reset form
setFormInput('');
event.preventDefault();
};
const toggleTodoState = index => event => {
const newTodosState = [...todos ]; // make copy
newTodosState[index].isComplete = !newTodosState[index].isComplete;
setTodos(newTodosState);
// Add functionality to update localStorage
};
const handleDelete = index => event => {
const newTodosState = [...todos.slice(0, index), ...todos.slice(index + 1) ];
setTodos(newTodosState);
// Add functionality to update localStorage
}
// Render
return (<div>
<h3>Todos</h3>
<ul>
{todos.map((item, index) => <li key={`todo-${index}`}>{item.todo} - <input type="checkbox" checked={item.isComplete} onClick={toggleTodoState(index)} /> - <button onClick={handleDelete(index)}>Delete</button></li>)}
</ul>
<hr />
<form onSubmit={handleSubmit}>
<input type="text" value={formInput} onChange={handleChange} placeholder="Enter todo name" />
<button type="submit">Add</button>
</form>
</div>);
};
ReactDOM.render(<Example />, document.querySelector('#root'));
<body>
<div id="root"></div>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script type="text/babel" src="main.js"></script>
</body>
Upvotes: 1