I'm new to REACT.. I'm trying todo list websites based on the video for beginner react. But some function that has been use in REACT, which is id, I cannot understand
todo listList.length === 0 ? 1 : todoList[todoList.length -1].id + 1,
this function is assigned to add tasks in add list, but I don't know how it works using this code. If someone can kindly explain this to me, it will be a great of help. I keep on repeating on this part in the video, but still, it is hard to understand. Thank you.
This is the full code for app.js
import "./App.css";
import {useState} from "react";
import { Task } from "./Task"
function App() {
const [todoList, setTodoList] = useState([]);
const [newTask, setNewTask] = useState("");
const handleChange = (event) => {
setNewTask(event.target.value);
};
const addTask = () => {
const task = {
id: todoList.length === 0 ? 1 : todoList[todoList.length -1].id + 1,
taskName: newTask,
completed: false,
};
setTodoList([...todoList, task]);
};
const deleteTask = (id) => {
setTodoList(todoList.filter((task) => task.id !== id));
};
const completeTask = (id) => {
setTodoList(
todoList.map((task)=> {
if(task.id === id) {
return { ...task, completed: true };
} else {
return task;
}
})
);
};
return (
<div className="App">
<div className="addTask">
<input onChange={handleChange} />
<button onClick={addTask}> Add task</button>
</div>
<div cl assName="list">
{todoList.map((task) => {
return (
<Task
taskName={task.taskName}
id={task.id}
completed={task.completed}
deleteTask={deleteTask}
completeTask={completeTask}
/>
);
})}
</div>
</div>
);
}
export default App;
This is for task.js
export const Task = (props) => {
return (
<div
className="task"
style={{ backgroundColor: props.completed ? "pink" : "white"}}
>
<h1>{props.taskName}</h1>
<button Onclick={() => props.completeTask(props.id)}> Complete </button>
<button onClick={() => props.deleteTask(props.id)}> X </button>
</div>
);
}
Okay, it's really simple. You need to generate a special id for every task. one of the easiest ways you can do that is to generate an id from 1 to how many you want or need. here todoList.length === 0 ? 1 : todoList[todoList.length -1].id + 1 you first check if there is any task assigned or not. if not you generate the first id and it's 1. from here each task you add will get the last task id and add 1 number to it. so by this definition, the first task will have 1 as id and the second will have 2 as id.
Related
I made my basic ToDo list site after learning react. I wanted to add a function of saving the items on reload. I am a beginner in react so I am facing difficulty in this. I tried the following code:
import React from "react";
import ToDoList from "./components/ToDoList";
import Navbar from './components/Navbar'
import '../src/App.css'
export default function TodoInput() {
const saveLocalTasks = () => {
let savedTasks = localStorage.getItem('tasks')
console.log(savedTasks)
if (savedTasks) {
return JSON.parse(localStorage.getItem('tasks'))
} else {
return []
}
}
const [task, setTask] = React.useState('')
const [count, setCount] = React.useState(0)
const [taskList, setTaskList] = React.useState([saveLocalTasks()])
const [disable, setDisable] = React.useState(true)
const [viewTaskList, setViewTaskList] = React.useState(true)
const updateTaskList = () => {
setTaskList([...taskList, {object: task, key: Date.now()}])
setTask('')
setViewTaskList(false)
setCount(count + 1)
setDisable(true)
}
const inputValue = e => {
setTask(e.target.value)
e.target.value === '' || task === '' || task.length === 0
?
setDisable(true)
:
setDisable(false)
}
// console.log(task.length)
React.useEffect(() => {
localStorage.setItem('tasks', JSON.stringify(taskList.object))
}, [taskList])
return (
<div>
<Navbar />
<header>
<div className="todolist-border">
<div className="todo-input-form">
<input
className = "inputText"
placeholder="Add a Task"
value={task}
onChange = {inputValue}
/>
<button disabled = {disable} onClick = {updateTaskList} className="todo-add-button">+</button>
</div>
{
viewTaskList || count === 0
?
<div className="pendingTasks-div">
<img className = "pending-task-image"
src= "https://dm0qx8t0i9gc9.cloudfront.net/watermarks/image/rDtN98Qoishumwih/task-pending-cartoon-business-vector-illustrations_zJCs81OO_SB_PM.jpg"
alt="pending-tasks" />
<p className="no-task-message">There are no pending tasks!! #Enjoy🥳🥳</p>
</div>
:
<ToDoList count = {count} setCount = {setCount} task = {task} taskList = {taskList} setTaskList = {setTaskList}/>
}
</div>
</header>
</div>
)
}
But the following error is coming up:
The following is the code for ToDoList component:
import React from "react";
export default function ToDoList(props) {
const deleteTaskListItem = (key) => {
const updatedList = props.taskList.filter((item) => {
return (
item.key !== key
)
})
props.setTaskList(updatedList)
props.setCount(props.count - 1)
}
return(
<div>
{props.taskList.map((item) => {
return (
<div key = {item.key} className="todolist-div">
<input type="checkbox" className="list-checkbox">
</input>
<p>{item.object}</p>
<button onClick={()=>deleteTaskListItem(item.key)} className="delete-button">X</button>
</div>
)
})}
</div>
)
}
Kindly suggest a method to add this feature.
The above error happens when you try to JSON.parse undefined. Check this link. Here, I tried to do some changes in your code on CodeSandbox. There you can find some changes I have made.
Firstly, you shouldn't try to set data in this useState const [taskList, setTaskList] = React.useState([saveLocalTasks()]). You should set data in useEffect.
In the following code, you are trying to save taskList.object but taskList is an array. The below code will throw an error.
React.useEffect(() => {
localStorage.setItem('tasks', JSON.stringify(taskList.object))
}, [taskList])
As you asked in your question, you want to try to save data when the user reloads the window. You can achieve this by using window.onbeforeunload event (line 48).
Hope the above will help you.
P.S: The codesandbox code I shared isn't fully functional. I have made just some changes that will help you to go ahead with your coding. Thank you.
I have an input whose value is 4 or it can be any one and when I press the button it is generating another dynamic input, what I need is that each time a dynamic input is generated the value of each input is subtracted by -1 until it is in 1.
I have not been able to make it work as I need it, if someone can help me I would be very grateful, I have reviewed several examples but I have not been able to make it work, any help is welcome.
import { useState } from "react";
const defaultState = {
nombre: 4
};
function Row({ onChange, onRemove, nombre }) {
return (
<div>
<input
value={nombre}
onChange={e => onChange("nombre", e.target.value)}
placeholder="Decrementar"
/>
<button onClick={onRemove}>Eliminar</button>
</div>
);
}
export default function Pruebas() {
const [rows, setRows] = useState([defaultState]);
const handleOnChange = (index, name, value) => {
const copyRows = [...rows];
copyRows[index] = {
...copyRows[index],
[name]: value
};
setRows(copyRows);
};
const handleOnAdd = () => {
setRows(rows.concat(defaultState));
};
const handleOnRemove = index => {
const copyRows = [...rows];
copyRows.splice(index, 1);
setRows(copyRows);
};
return (
<div className="App">
{rows.map((row, index) => (
<Row
{...row}
onChange={(name, value) => handleOnChange(index, name, value)}
onRemove={() => handleOnRemove(index)}
key={index}
/>
))}
<button onClick={handleOnAdd}>-</button>
</div>
);
}
When you concatenate a new row make sure to decrement the field nombre's value see for a full example : https://codesandbox.io/s/stack-over-inc-3nixd
I am trying to remove an input field with filter function but it's not working.
In the following code add operation works fine but remove operation is not working properly ,it is not removing the corresponding element.Another problem the values on the inputs fields not present when the component re-renders.so experts guide me how i can achieve removing the corresponding row when the remove button is clicked and the input values should not be reset when the component re-renders
So when I refresh the page and click to remove an input it will clear all other input data. How can I fix this problem ?
Update adding full component in question:
const Agreement = (props) => {
const { agreement, editable, teamData, teamId, fetchTeamData } = props;
const [editing, setEditing] = useState(false);
const [title, setTitle] = useState("");
const [showErrors, setShowErrors] = useState(false);
const [errorsArr, setErrorsArr] = useState();
const initialFormState = {
rule_0: teamData.rules.rule_0,
rule_1: teamData.rules.rule_1,
rule_2: teamData.rules.rule_2,
rule_3: teamData.rules.rule_3,
creator: teamData.User.public_user_id,
};
const [updateTeamData, setUpdateTeamData] = useState(initialFormState);
const [inputs, setInputs] = useState(
teamData.rules.map((el) => ({
...el,
guid: uuidV4(),
}))
);
const handleChange = (event) => {
const { name, value } = event.target;
// Update state
setUpdateTeamData((prevState) => ({
...prevState,
[name]: value,
}));
};
// Add more input
const addInputs = () => {
setInputs([...inputs, { name: `rule_${inputs.length + 1}` }]);
};
// handle click event of the Remove button
const removeInputs = (index) => {
const newList = inputs.filter((item, i) => index !== i); // <-- compare for matching index
setInputs(newList);
};
const clearInput = (dataName) => {
setUpdateTeamData((prevState) => {
delete prevState[dataName];
return {
...prevState,
};
});
};
const handleSubmit = async (event) => {
event.preventDefault();
setEditing(false);
// Send update request
const res = await axios.put(`/api/v1/teams/team/${teamId}`, updateTeamData);
// If no validation errors were found
// Validation errors don't throw errors, it returns an array to display.
if (res.data.validationErrors === undefined) {
// Clear any errors
setErrorsArr([]);
// Hide the errors component
setShowErrors(false);
// Call update profiles on parent
fetchTeamData();
} else {
// Set errors
setErrorsArr(res.data.validationErrors.errors);
// Show the errors component
setShowErrors(true);
}
};
const handleCancel = () => {
setEditing(false);
};
useEffect(() => {
if (agreement === "default") {
setTitle(defaultTitle);
// setInputs(teamData.rules);
} else {
setTitle(agreement.title ?? "");
}
}, [agreement, teamData]);
// console.log("teamData.rules", teamData);
console.log("inputs", inputs);
return (
<div className="team-agreement-container">
{!editing && (
<>
<h4 className="team-agreement-rules-title">{title}</h4>
{editable && (
<div className="team-agreement-rules">
<EditOutlined
className="team-agreement-rules-edit-icon"
onClick={() => setEditing(true)}
/>
</div>
)}
{teamData.rules.map((rule, index) => (
<div className="team-agreement-rule-item" key={`rule-${index}`}>
{rule ? (
<div>
<h4 className="team-agreement-rule-item-title">
{`Rule #${index + 1}`}
</h4>
<p className="team-agreement-rule-item-description">
- {rule}
</p>
</div>
) : (
""
)}
</div>
))}
</>
)}
{/* Edit rules form */}
{editing && (
<div className="team-agreement-form">
{showErrors && <ModalErrorHandler errorsArr={errorsArr} />}
<h1>Rules</h1>
{inputs.map((data, idx) => {
return (
<div className="agreement-form-grid" key={data.guid}>
<button
type="button"
className="agreement-remove-button"
onClick={() => {
removeInputs(idx);
clearInput(`rule_${idx}`);
}}
>
<Remove />
</button>
<input
name={`rule_${idx}`}
onChange={handleChange}
value={teamData.rules[idx]}
/>
</div>
);
})}
{inputs.length < 4 && (
<div className="team-agreement-add-rule">
<button type="submit" onClick={addInputs}>
<Add />
</button>
</div>
)}
<div className="div-button">
<button className="save-button" onClick={handleSubmit}>
Save
</button>
<button className="cancel-button" onClick={handleCancel}>
Cancel
</button>
</div>
</div>
)}
</div>
);
};
export default Agreement;
When i do console.log(inputs) this is the data that I got:
0: 0: "t" 1: "e" 2: "s" guid: "e18595a5-e30b-4b71-8fc2-0ad9c0e140b2"
proto: Object 1: 0: "d" 1: "a" 2: "s" 3: "d" 4: "a" 5: "s" guid: "537ca359-511b-4bc6-9583-553ea6ebf544" ...
Issue
The issue here is that you are using the array index as the React key. When you mutate the underlying data and reorder or add/remove elements in the middle of the array then the elements shift around but the React key previously used doesn't move with the elements.
When you remove an element then all posterior elements shift forward and the index, as key, remains the same so React bails on rerendering the elements. The array will be one element shorter in length and so you'll see the last item removed instead of the one you actually removed.
Solution
Use a React key that is intrinsic to the elements being mapped, unique properties like guids, ids, name, etc... any property of the element that guarantees sufficient uniqueness among the dataset (i.e. the siblings).
const [inputs, setInputs] = useState(teamData.rules);
const removeInputs = (index) => {
// compare for matching index
setInputs(inputs => inputs.filter((item, i) => index !== i));
};
{inputs.map((data, idx) => {
return (
<div className="agreement-form-grid" key={data.id}> // <-- use a unique property
<button
type="button"
className="agreement-remove-button"
onClick={() => {
removeInputs(idx);
clearInput(`rule_${idx}`);
}}
>
<Remove />
</button>
<input
name={`rule_${idx}`}
onChange={handleChange}
value={teamData.rules[idx]}
/>
</div>
);
})}
If your teamData.rules initial state value doesn't have any unique properties to use then you can map this to a new array and add a sufficient id property.
const [inputs, setInputs] = useState(teamData.rules.map(el => ({
...el,
guid: generateId()***,
})));
*** this is a function you need to define yourself, or import from a module like uuid***
import { v4 as uuidV4 } from 'uuid';
...
const [inputs, setInputs] = useState(teamData.rules.map(el => ({
...el,
guid: uuidV4(),
})));
// Add more input
const addInputs = () => {
setInputs(inputs => [
...inputs,
{
name: `rule_${inputs.length + 1}`,
guid: uuidV4();
},
]);
};
Then when mapping use the guid property.
<div className="agreement-form-grid" key={data.guid}>
The issue is because you are trying to compare index with array item in filter method. You should use the second argument in filter which denotes the array index of the current iterating item
const removeInputs = (index) => {
const newList = inputs.filter((item,i) => index !== i);
setInputs(newList);
};
That's your solution, you are trying with item but you are comparing it with index that's wrong. You should do it like this,
const newList = inputs.filter((item, key) => index !== key);
I'm new to React and I have a short and stupid question, but my poor phrasing makes it so that I haven't been able to find the answer by searching for it.
Basically, I have 2 password fields. I want to show and hide each one independently, but I would like a more elegant way than having 2 different functions with their own variables like this:
const [showPassword1, setShowPassword1] = useState(false);
const [showPassword2, setShowPassword2] = useState(false);
const togglePasswordVisiblity1 = () => {
setShowPassword1(showPassword1 ? false : true);
};
const togglePasswordVisiblity2 = () => {
setShowPassword2(showPassword2 ? false : true);
};
With the respective buttons below:
<span onClick={togglePasswordVisiblity1}>Show/Hide</span>
<span onClick={togglePasswordVisiblity2}>Show/Hide</span>
I'm sure there's a way to regroup these into a single function that changes the right variable based on which span is clicked, but I haven't had any luck finding the syntax. Sorry again for this question, hopefully it can be answered quickly!
Thanks in advance for your help.
You can try to use an array for the state. Check this sandbox demo:
import React, { useState } from "react";
export default function App() {
const [showPassword, setShowPassword] = useState([false, false]);
return (
<div className="App">
<button
onClick={() => setShowPassword([!showPassword[0], showPassword[1]])}
>
{JSON.stringify(showPassword[0])}
</button>
<button
onClick={() => setShowPassword([showPassword[0], !showPassword[1]])}
>
{JSON.stringify(showPassword[1])}
</button>
</div>
);
}
Refactored version by extracting state update into a function:
import React, { useState } from "react";
export default function App() {
const [showPassword, setShowPassword] = useState([false, false]);
const togglePassword = (idx) => {
const newShowPassword = [...showPassword];
newShowPassword[idx] = !newShowPassword[idx]; // toggle
setShowPassword(newShowPassword); // update the state
};
return (
<div className="App">
<button onClick={() => togglePassword(0)}>
{JSON.stringify(showPassword[0])}
</button>
<button onClick={() => togglePassword(1)}>
{JSON.stringify(showPassword[1])}
</button>
</div>
);
}
const [state , setState] = useState({
showPassword1:false,
showPassword2: false
})
const togglePasswordVisiblity1= e => {
const {name , value} = e.target
setState( prevState => ({
...prevState,
[name]: prevState[name] ? false : true
}))
}
//
<span name='showPassword1' onClick={togglePasswordVisiblity1}>Show/Hide</span>
<span name='showPassword2' onClick={togglePasswordVisiblity1}>Show/Hide</span>
I am working on a grocery list project. With this project, when the user enters an item, I want to give the ability to edit said item. I am storing everything inside an array of objects in my state. The structure of my objects is:
{
product: 'toast',
category: 'bakery',
quantity: 3,
type: 'each
},
{
product: 'apple',
category: 'produce',
quantity: 2,
type: 'each'
},
{
product: 'hamburger',
category: 'meat',
quantity: 1,
type: 'Lb'
}
What I want to be able to do is have the user select one of those objects inside a card type function, then update it. Currently, I can add items to the list, but I can not update them.
I have tried setList(list[i].txt=v) and setList(list=>list[i].product=v) plus other variations trying to target the specific object. Any ideas would be greatly appreciated.
The following is my main app.js code. NOTE: const Change() is where I am trying to update the object. The variables that I am passing in come from my item.js code
import React ,{useState,useEffect} from 'react';
import List from './components/list';
import Header from './components/header';
function App() {
const [list, setList] = useState([]);
const Add = (p, c, q, t, crt) => {
console.log({product: p, category: c, quantity: q, type: t, cart: crt})
setList(list=>[...list,{product:p, category:c, quantity:q, type:t, cart: crt}])
}
const Change = (i, txt, v) => {
//setList(list[i].txt=v)
console.log('id: ' + i + ' topic: ' + txt + ' value: ' +v)
setList(list=>list[i].product=v)
}
const Delete = () => {
}
return (
<div>
{console.log(list)}
<h1>Grocery List App</h1>
<Header add={Add}/>
<List set={setList} lst={list} chg={Change} del={Delete} />
</div>
);
}
export default App;
This next code is my list.js file. I am iterating over my list state and creating the individual 'cards' for each item.
import React from 'react';
import Card from './item';
const List = (props) => {
const productChange = (txt, v) => {
console.log(props.lst[v].product)
}
const quantityChange = () => {
}
const cartChange = () => {
}
return(
<div>
<p>To Find:</p>
<ul>
{ props.lst.map((item, index) =>
item.cart === false ?
<Card
key={item.index}
index={index}
value={index}
cart={item.cart}
item={item.product}
units={item.quantity}
unitType={item.type}
cartChange={cartChange}
itemChange={productChange}
quantityChange={quantityChange}
change={props.chg}
delete={props.del}/>
: null)
}
</ul>
<p>Found</p>
<ul>
{ props.lst.map((item, index) =>
item.cart === true ?
<Card
key={item.index}
index={index}
value={index}
cart={item.cart}
item={item.product}
units={item.quantity}
unitType={item.unit}
cartChange={cartChange}
itemChange={productChange}
quantityChange={quantityChange}
change={props.chg}
delete={props.del}/>
: null)
}
</ul>
</div>
)
}
export default List;
This is the code for item.js. This is the final spot where I display the information from the list. NOTE: change() from the first file is getting called here when I change the text of an input.
import React from 'react';
const Card=(props)=>{
return (
<li key={props.value}>
<div>
<input
type="checkbox"
checked={props.cart}
onChange={(e)=> {props.cartChange(props.value)}} />
</div>
<div>
<input
id={'product '+ props.value}
className='update'
type='text'
value={props.item}
onChange={(e) =>
props.change(props.value,'product', e.target.value)
}
/>
<br/>
<input
id='quantityValue'
className='update'
type='number'
value={props.units}
// onChange={(e)=>
props.quantityChange(e.target.value, props.value)}
/>
<span id='quantityType' className='update'>{props.unitType}
</span>
</div>
<div>
<button
id='save-button'
type='button'
onClick={(e) => { props.change(
props.item,
props.units,
props.unitType,
props.value)
}
}>✓ save</button>
<button
id='delete-button'
type='button'
onClick={(e) => {props.delete(props.value)}}>✗ delete</button>
</div>
</li>
)
}
export default Card;
you want to call setList with a list where you just amend that one object.
You can use list.map() for this. Ideally you want to add ID field to your objects.
But even without ID you can use index:
setList(list.map((product, index)=>{index == i ? v : product}))
or more verbose:
const Change = (i, txt, v) =>{
const newList = list.map((product, index) => {
return index == i ? v : product
});
setList(newList);
}
You want to implement change function. Is this right?
Try this one.
const Change = (i,txt,v) =>{
setList(list.map((e, ei) => {
if (ei === i) {
e.product = v;
return e;
}
return e;
}));
}