23
loading...
This website collects cookies to deliver better user experience
<ul>
tag to display list item. Yes, we can make an order list by using <ol>
but in this guide we wanna know how to manage states with useState so let's move on with <ul>
.// App component to handle input form with the logic
import './App.css';
import Overview from './components/Overview';
import { useState } from 'react';
import uniqid from "uniqid";
function App() {
const [task, setTask] = useState({
text: '',
order: 1,
id: uniqid()
});
const [tasks, setTasks] = useState([]);
// order remains regardless of how much input changes
// order changes its value only when form is submitted or an item deleted
const inputChangeHandler = (e) =>{
setTask((prevTask) => ({
...prevTask,
text: e.target.value
}));
}
const submitHandler = (e) => {
e.preventDefault();
// Avoid setTask right before setTasks whose value depends on Task !!!
setTasks((prevTasks) => [...prevTasks, task]);
setTask((prevTask) => ({
text: '',
order: prevTask.order + 1,
id: uniqid()
}))
}
const deleteHandler = (e) => {
const id = e.target.parentNode.id;
let deletedAt;
// Remove target item
let reducedList = tasks
.filter((task, index) => {
if(task.id == id){
deletedAt = index;
return false;
}
return true;
})
.map((item, index) => {
if(index >= deletedAt) return {...item, order: item.order -1};
else return item;
})
// Update tasks
setTasks([...reducedList]);
// clear text field, decrease order after item deleted
setTask({
text: '',
order: task.order - 1,
id: uniqid()
})
}
return (
<>
<form onSubmit={submitHandler}>
<input type="text" id="taskInput" value={task.text} onChange={inputChangeHandler} placeholder="Create a task"></input>
<button type="submit">Submit</button>
</form>
<Overview tasks={tasks} handleDelete={deleteHandler}/>
</>
)
}
export default App;
const [task, setTask] = useState({
text: "",
order: 0,
id: uniqid()
});
const [tasks, setTasks] = useState([]);
useState(initialValue)
returns a pair of value [state, setState]
. initialValue can be anything, from a number, a string to an obj or an array. setState
is an updater function. Here I declare task
to manage a single to-do item data and tasks
to keep track of many items.<input
...
value={task.text}
></input>
setState
function to set or update a state, whatever returned by this function is set as a new state value.setState
has two forms. The first one is by passing a new value as an argument: setState(newStateValue)
. Refer to line 65 where we update tasks
array by passing in a new array:let reducedList = tasks.filter(...)
// Update tasks
setTasks([...reducedList]);
// May fail to update
setTask({...task, text: ''});// update text, others unchanged
order
and id
properties to be unchanged for every onChange events. It means we're going to just update task
partially instead of an entirely new state. In this circumstance, the second form of setState()
comes in.setState((state, props) => newValue)
const inputChangeHandler = (e) => {
setTask((prevTask) => ({
...prevTask,
text: e.target.value
}));
};
setTasks
works exactly the same as setTask
:const submitHandler = (e) => {
e.preventDefault();
// Avoid setTask right before setTasks whose value depends on Task's value !!!
setTasks((prevTasks) => [...prevTasks, task]);
setTask((prevTask) => ({
text: '',
order: prevTask.order + 1,
id: uniqid()
}))
}
deleteHandler
as below:const deleteHandler = (e) => {
const id = e.target.parentNode.id;
let deletedAt;
// Remove target item
let reducedList = tasks
.filter((task, index) => {
if(task.id == id){
deletedAt = index;
return false;
}
return true;
})
.map((item, index) => {
if(index >= deletedAt) return {...item, order: item.order -1};
else return item;
})
// Update tasks
setTasks([...reducedList]);
// clear text field, decrease order after item deleted
setTask({
text: '',
order: task.order - 1,
id: uniqid()
})
}
Tasks
so that we can make some modifications on this copy (remove item, update its value) on the side rather than set its state directly.setState
to safely update Tasks
tasks
like this:setTasks((prevTasks) => {
prevTasks.forEach((item, index) => {
if (index >= deletedAt){
item.order -= 1; // You are changing state directly
}
})
})
import React from "react";
import './Overview.css';
const Overview = (props) => {
return (
<ul className="task-list">
{props.tasks.map((item) => {
return (
<li key={item.id} id={item.id}>
<span>
{item.order}. {item.text}
</span>
<button onClick={props.handleDelete}>X</button>
</li>
);
})}
</ul>
);
};
export default Overview;
<ul>
to implement an ordered list. The order numbers get updated for every add/delete action. Here we use function map to dynamically render list item.State should be treated as immutable. Never set the state directly like state.value++. Always rely on setState
to manage state, avoid unexpected results and bugs.
State updates may be async. If your new state value is calculated based the old state, use the second form of setState
where you pass in a function. If your new state value is independent from the previous state, feel free to use to first form of it setState(newValue)