How to update object property in useState hook? - reactjs

I want to when the user clicks submit button it should add new object. with the given value which is stored in list varaible.It should not delete the old objects,only name Property should be added.
import { useState } from "react";
export default function Add() {
const [todo, addTodo] = useState([{ name: "cat", time: "2 minutes ago" }]);
const [list, setList] = useState();
const add = () => {
addTodo({...todo, todo: {
name: list
}})
};
return (
<div>
<input
type="text"
value={list}
onChange={(e) => setList(e.target.value)}
/>
<button onClick={add}>Add new</button>
{todo.map((item, i) => (
<li key={i}>
{item.name}
<span style={{ marginLeft: "15px" }}>{item.time}</span>
</li>
))}
</div>
);
}

When your state update is dependent on the previous state value ,its a good practice to make the state updater to have an inline function .
addTodo(existingTodos => ([...existingTodos, { name: list}] ))
Here we are saying , get the exisitingTodos list and append the new todo along with the exisitingTodos .
const add = () => {
addTodo(existingTodos => ([...existingTodos, { name: list}] ))
};

Because todo is an array so you should pass an array when call addTodo
addTodo([
...todo,
{
name: list,
},
]);

Related

Nested state (react) with onclick events

I was given the task to build a recursive tree with some functionality. I managed to build a tree using a recursive component, I attach the code below.
function App() {
const [data, setData] = useState([{
id: 1,
name: "node 1",
children: [{
id: 2,
name: "node 1.1",
children: [{
id: 8,
name: "node 1.1.1",
children: []
}]
},{
id: 3,
name: "node 1.2",
children: [{
id: 4,
name: "node 1.2.1",
children: [{
id: 5,
name: "node 1.2.1.1",
children: []
}]
}]
}]
}])
return (
<div className="App">
<Tree data = {data} setData = {setData} margin={15}/>
<button>Add child</button>
</div>
);
}
and
const Tree = ({data, margin, setData}) => {
return (
<Fragment>
{data.map((element, index) => (
<div>
<div className="tier">
<div
style={{marginLeft: `${margin}px`}}
>
{element.name}
</div>
</div>
{element.children && element.children.length ? <Tree
data={element.children}
setData={setData}
margin={margin + 15}
/> : false}
</div>))}
</Fragment>
);
};
I need to add a node selection when clicking on it. I have an idea about the implementation: create a state in which the ID of the desired node will be stored, and design it using CSS.
But I have absolutely no idea how to add a child node to the selected node when the button is clicked.
You did half of the job and your idea about storing id is the best overall, you could actually store the selected item itself but it will be not as great as it sounds due to mutation of this item, you will not be able to trigger dependent hooks update for it.
So you only need to store the id of selected item and a few utility things to simplify the work with the tree. On the code below you can see the flatTree that was calculated with useMemo from the original data. It just flattens your tree to an array, so you will be able to easilly find real selected item and to calculate the next id that will be inserted on button click. And to add a few more props to the Tree component itself. onClick to handle actual click and selectedTreeItem just to add some classes for selected item (optional).
import "./styles.css";
import { Fragment, useEffect, useMemo, useState } from "react";
export default function App() {
const [data, setData] = useState([{id:1,name:"node 1",children:[{id:2,name:"node 1.1",children:[{id:8,name:"node 1.1.1",children:[]}]},{id:3,name:"node 1.2",children:[{id:4,name:"node 1.2.1",children:[{id:5,name:"node 1.2.1.1",children:[]}]}]}]}]);
const [selectedTreeItemId, setSelectedTreeItemId] = useState();
const flatTree = useMemo(() => {
const flat = (item) => [item, ...(item.children || []).flatMap(flat)];
return data.flatMap(flat);
}, [data]);
const selectedTreeItem = useMemo(() => {
if (!selectedTreeItemId || !flatTree) return undefined;
return flatTree.find((x) => x.id === selectedTreeItemId);
}, [flatTree, selectedTreeItemId]);
const getNextListItemId = () => {
const allIds = flatTree.map((x) => x.id);
return Math.max(...allIds) + 1;
};
const onTreeItemClick = ({ id }) => {
setSelectedTreeItemId((curr) => (curr === id ? undefined : id));
};
const onAddChildClick = () => {
if (!selectedTreeItem) return;
if (!selectedTreeItem.children) selectedTreeItem.children = [];
const newObj = {
id: getNextListItemId(),
name: `${selectedTreeItem.name}.${selectedTreeItem.children.length + 1}`,
children: []
};
selectedTreeItem.children.push(newObj);
// dirty deep-clone of the tree to force "selectedTreeItem"
// to be recalculated and its !reference! to be updated.
// so any hook that has a "selectedTreeItem" in depsArray
// will be executed now (as expected)
setData((curr) => JSON.parse(JSON.stringify(curr)));
};
useEffect(() => {
console.log("selectedTreeItem: ", selectedTreeItem);
}, [selectedTreeItem]);
return (
<div className="App">
<Tree
data={data}
margin={15}
onTreeItemClick={onTreeItemClick}
selectedTreeItem={selectedTreeItem}
/>
<button
type="button"
disabled={!selectedTreeItem}
onClick={onAddChildClick}
>
Add child
</button>
</div>
);
}
const Tree = ({ data, margin, onTreeItemClick, selectedTreeItem }) => {
return (
<Fragment>
{data.map((element) => (
<div key={element.id}>
<div
className={`tier ${
selectedTreeItem === element ? "selected-list-item" : ""
}`}
onClick={() => onTreeItemClick(element)}
>
<div style={{ marginLeft: `${margin}px` }}>
{element.id}: {element.name}
</div>
</div>
{element.children?.length > 0 && (
<Tree
data={element.children}
margin={margin + 15}
onTreeItemClick={onTreeItemClick}
selectedTreeItem={selectedTreeItem}
/>
)}
</div>
))}
</Fragment>
);
};
Nested array in your date tree you should map as well. It should look like that
{element.children?.map(el, I => (
<p key={I}> {el. name}</p>
{el.children?.map(child, ind => (
<p key={ind}>{child.name}</p>
)}
)}
the ? is very important, because it would work only if there was a date like that.

react.js react-select onselected value change another select value and submit multiple items to array

I have stomped on a problem that i don't know how to resolve.
I have a react-select input that passes a selected value to another select input.
When a user clicks on submit button then i have to display an array of every selector input that the user has selected as a list of items and then the form should reset all select values.
For this, i have tried submitting to an array but it only shows one item from all selectors and form doesn't reset its values.
Here is a sandbox link
https://codesandbox.io/s/awesome-carson-i99g8p?file=/src/App.js
How can I archive this I have tried everything but I could not figure out how can i archive this functionality.
Ok. First tricky thing of react-select is, the value you assign to the component must be an object with label and valueproperties, not just the value. Meaning, when handling change events, you should set the state using the full event object.
This is the code mostly fixed:
import React, { useState, useEffect } from "react";
import Select from "react-select";
const options = [
{ value: "0", label: "0" },
{ value: "1", label: "1" },
{ value: "2", label: "2" }
];
const options2 = [
{ value: "Before Due Date", label: "Before Due Date" },
{ value: "After Due Date", label: "After Due Date" }
];
const App = (props) => {
const [numOfDays, setNumOfDays] = useState('');
const [beforeDueDate, setBeforeDueDate] = useState('');
const [items, setItems] = useState([]);
const [reminders, setReminders] = useState([]);
const submit = (event) => {
event.preventDefault(); // <-- prevent form submit action
const obj = {};
obj.id = reminders.length + 1;
obj.numOfDays = numOfDays.value;
obj.beforeDueDate = beforeDueDate.value;
setReminders((items) => [...items, obj]); // <-- update arr state
setBeforeDueDate("");
setNumOfDays("");
};
function numOfDaysHandle(event) {
// const numOfDays = event.value;
setNumOfDays(event);
setItems((items) => [...items, items]);
}
function beforeDueDateHandle(event) {
// const value = event.value;
setBeforeDueDate(event);
}
const removeReminder = (id) => {
setReminders(reminders.filter((item) => item.id !== id));
};
return (
<>
<form>
<div>
{reminders.map((item, index) => (
<div key={index}>
<div>
<span>
{item.numOfDays} days {item.beforeDueDate}
</span>
<button onClick={() => removeReminder(item.id)}>
removeItem
</button>
</div>
</div>
))}
</div>
<div>
<Select
options={options}
value={numOfDays}
id="numOfDays"
placeholder="Days"
isSearchable={false}
//onChange={numOfDaysHandle}
onChange={numOfDaysHandle}
/>
<Select
options={options2}
value={beforeDueDate}
id="beforeDueDate"
placeholder="Before Due Date"
isSearchable={false}
onChange={beforeDueDateHandle}
/>
</div>
{items.map((item, index) => (
<div key={index}>
<Select
options={options}
value={item.numOfDays}
id="numOfDays"
placeholder="Days"
isSearchable={false}
onChange={numOfDaysHandle}
/>
<Select
options={options2}
value={item.beforeDueDate}
id="beforeDueDate"
placeholder="Before Due Date"
isSearchable={false}
onChange={beforeDueDateHandle}
// onClick={() => setItems((items) => [...items, items])}
/>
</div>
))}
<button
onClick={submit}
//disabled={!numOfDays}
>
Set Reminder
</button>
</form>
</>
);
};
export default App;
Please see if you can move forward now, I could not understand what you want exactly with the array of <Select /> elements.

Delete a row from array by component event in that array using React

Below is a basic example of the problem I'm experiencing.
I have a list of items that are in an array. The user can add another item to the list, and it shows on the page. Each item has a delete button and that delete button is a component inside the array item (this is so each item could have a button that does a different action.. in my example the action is deleting, but later it might be "Send" or "Delete" or "Cancel" or "Edit"...)
The trouble is, when I click the "Action" in this case delete, I want to know which item in the array this was. This way, I can get the array index and delete it. Or later grab additional details from the
import React, {useState} from "react"
function App() {
const [list, setList] = useState([])
function addRow(){
let newRow = {
name: "Test",
action: <span onClick={e=>removeThisRow()}>REMOVE</span>
}
setList([
...list,
newRow
])
}
function removeThisRow(){
// need this to remove the specific item from my list array...
console.log("removing...")
}
return (
<div>
{
list.map(item=>(
<div>
{item.name} | {item.action}
</div>
))
}
<div onClick={e=>addRow()}>ADD ROW</div>
</div>
);
}
export default App;
Working and tested
function App() {
const [list, setList] = React.useState([])
function addRow(){
let newRow = {
id: Math.random(),
name: `Test-${Math.random()}`,
action: (id) => <span onClick={()=>removeThisRow(id)}>REMOVE</span>
}
setList([
...list,
newRow
])
}
function removeThisRow(id){
setList(l => l.filter(li => li.id !== id))
}
return (
<div>
{
list.map((item)=>(
<div key={item.id}>
{item.name} | {item.action(item.id)}
</div>
))
}
<div onClick={e=>addRow()}>ADD ROW</div>
</div>
);
}
Id is just random number, I would use something more unique like uuid()
Here's an alternative approach to dynamically adding/editing/removing items from an array. Instead of unnecessarily creating JSX within an array, you can map over the array list and edit/remove the row based upon a unique id.
You can get even more sophisticated by adding two additional input toggles before adding a new row. These two toggles might add an isEditable and isRemovable properties to the row when when it's created. These properties can then be used to dynamically include or exclude buttons when the list is displayed. This is a cleaner approach as you're not recreating the same buttons over and over for each row, but flexible enough to conditionally render them.
On a related note, using the array index as a key is anti-pattern.
Demo Source Code:
Demo: https://q3wph.csb.app/
Code:
import * as React from "react";
import { v4 as uuid } from "uuid";
import Button from "./components/Button";
import Input from "./components/Input";
import Switch from "./components/Switch";
import "./styles.css";
export default function App() {
const [list, setList] = React.useState([]);
const [editRow, setEditRow] = React.useState({
id: "",
value: ""
});
const [newRowValue, setNewRowValue] = React.useState("");
const [newRowIsEditable, setNewRowEditable] = React.useState(true);
const [newRowIsRemovable, setNewRowRemovable] = React.useState(true);
const removeItem = (removeId) => {
setList((prevState) => prevState.filter(({ id }) => id !== removeId));
};
const editItem = (editId) => {
const { name } = list.find((item) => item.id === editId);
setEditRow({ id: editId, value: name });
};
const updateRowItem = ({ target: { value } }) => {
setEditRow((prevState) => ({ ...prevState, value }));
};
const handleRowUpdate = (e) => {
e.preventDefault();
const { id, value } = editRow;
if (!value) {
alert("Please fill out the row input before updating the row!");
return;
}
setList((prevState) =>
prevState.map((item) =>
item.id === id ? { ...item, name: value } : item
)
);
setEditRow({ id: "", value: "" });
};
const addNewRowItem = (e) => {
e.preventDefault();
if (!newRowValue) {
alert("Please fill out the new row item before submitting the form!");
return;
}
setList((prevState) => [
...prevState,
{
id: uuid(),
name: newRowValue,
isEditable: newRowIsEditable,
isRemovable: newRowIsRemovable
}
]);
setNewRowValue("");
setNewRowEditable(true);
setNewRowRemovable(true);
};
return (
<div className="app">
<h1>Dynamically Add/Edit/Remove Row</h1>
{list.length > 0 ? (
list.map(({ id, name, isEditable, isRemovable }) => (
<div className="uk-card uk-card-default uk-card-body" key={id}>
{editRow.id === id ? (
<form onSubmit={handleRowUpdate}>
<Input
placeholder="Add a new row..."
value={editRow.value}
handleChange={updateRowItem}
/>
<Button color="secondary" type="submit">
Update Row
</Button>
</form>
) : (
<>
<h2 className="uk-card-title">{name}</h2>
{isEditable && (
<Button
className="uk-margin-small-bottom"
color="primary"
type="button"
handleClick={() => editItem(id)}
>
Edit
</Button>
)}
{isRemovable && (
<Button
color="danger"
type="button"
handleClick={() => removeItem(id)}
>
Remove
</Button>
)}
</>
)}
</div>
))
) : (
<div>(Empty List)</div>
)}
<form
className="uk-card uk-card-default uk-card-body"
onSubmit={addNewRowItem}
>
<Input
placeholder="Add a new row..."
value={newRowValue}
handleChange={(e) => setNewRowValue(e.target.value)}
/>
<Switch
label="Editable"
handleChange={(e) => setNewRowEditable(Boolean(e.target.checked))}
name="Editable"
value={newRowIsEditable}
/>
<Switch
label="Removable"
handleChange={(e) => setNewRowRemovable(Boolean(e.target.checked))}
name="Removable"
value={newRowIsRemovable}
/>
<Button
className="uk-margin-small-bottom"
color="secondary"
type="submit"
>
Add Row
</Button>
<Button color="danger" type="button" handleClick={() => setList([])}>
Reset List
</Button>
</form>
</div>
);
}
I'm attaching an attribute to the span, named index and accessing that in removeThisRow.
import "./styles.css";
import React, { useState } from "react";
function App() {
const [list, setList] = useState([]);
function addRow() {
let newRow = {
name: "Test",
action: (
<span index={list.length} onClick={(e) => removeThisRow(e)}>
REMOVE
</span>
)
};
setList([...list, newRow]);
}
function removeThisRow(e) {
// need this to remove the specific item from my list array...
const index = e.target.getAttribute("index"); // got the index as a string
// Do whatever you want
}
return (
<div>
{list.map((item) => (
<div>
{item.name} | {item.action}
</div>
))}
<div onClick={(e) => addRow()}>ADD ROW</div>
</div>
);
}
export default App;

How to handle the the onClick events on CheckBox in react js

I have an array called initialList the array contains three items these items have their own checkboxes, when I check on any of the items I want to push them into the list Array and at the same time remove them from the initialList array, I don't know what to write in the clickHandler function and how to render the list array
import React, { useState } from 'react'
function CheckBox3() {
const initialList = [{id: 1, name:'jim'},{id: 2, name: 'joe'},{id: 3, name: 'beth'}]
const [list, setList] = useState([])
const clickhandler = () => {
}
return (
<div>
<ol>
{initialList.map((item) => (
<li key={item.id}>{item.name}
<input type="checkbox" onClick={clickhandler} />
</li>))}
</ol>
</div>
)
}
export default CheckBox3
Good morning Neel.
if checkbox - there is only 2 possible values - boolean false/true.
So it's good idea to use negation here.
then U use onChange instead onClick:
<input type="checkbox" onChange={() => setChecked(!checked)}/>
I suggest to hold the actual checkbox value in state:
const [checked, setChecked] = useState()
Also if You have x checkboxes - there is need also for holding, which id of checkbox is in which state.
Or - just add checked parameter to your initialList object.
[
{id:1 checked: false}
{id:2 checked: true}
]
And also - for identifier You can add i to your map function - and in check function - reverse the value of i - id :
{initialList.map((item, i) => (
<li key={item.id}>{item.name}
<input type="checkbox" onClick={(i) =>check(i)} />
</li>))}
I recommend you to keep the list in the componente state, add/update the checked property every time an item is clicked:
function CheckBox3() {
const initialList = [{id: 1, name:'jim'},{id: 2, name: 'joe'},{id: 3, name: 'beth'}]
const [list, setList] = useState(initialList);
const clickhandler = ({target}) => {
const {checked, id} = target;
setList(prev => {
const clickedItem = prev.find(item => item.id.toString() === id);
clickedItem["checked"] = checked;
return [...prev];
});
}
return (
<div>
<ol>
{initialList.map((item) => (
<li key={item.id}>{item.name}
<input type="checkbox" id={item.id} onClick={clickhandler} />
</li>))}
</ol>
</div>
)
}
Then you can filter the selected items:
const selected = list.filter(item => item.checked);
This works perfectly for what you are trying to achieve.
What I did was I check if the checkbox is checked and if it's then push it to the list array and check if it item exists in the initialList and if it does too it should filter it out.
function CheckBox3() {
const [initialList, setIinitialList] = useState([{id: 1, name:'jim'},{id: 2, name: 'joe'},{id: 3, name: 'beth'}])
const [list, setList] = useState([])
const clickHandler = (e, item) => {
if (e.target.checked) {
setList(prevState => [
...prevState,
item
])
const newInitialList = initialList.filter((list, index) => list.id !== item.id);
setIinitialList(newInitialList)
}
}
useEffect(() => {
// Perform whatever you want to do when list changes here
console.log(list);
}, [list])
return (
<div>
<ol>
{initialList.map((item) => (
<li key={item.id}>{item.name}
<input type="checkbox" onClick={(e) => clickHandler(e, item)} />
</li>))}
</ol>
</div>
);
}

Callback inside a useState updater function in react Hooks

Im new to hook and so is react,Ive been watching some tutorials lately,I saw Ben awad's video for dynamic forms and I tried replicating it.There he used a callback inside the useState updater function which seems new to me.He used link setPeople(currentPeople => {}) What is the argument currentPeople come from and why its used,Can you someone please explain,Thanks in advance!
import { useState } from "react";
import "./App.css";
import { generate } from "shortid";
interface Person {
id: string;
firstName: string;
lastName: string;
}
function App() {
const [people, setPeople] = useState<Person[]>([
{
id: "5",
firstName: "Aashiq",
lastName: "Ahmed",
},
]);
return (
<>
<h2 style={{ textAlign: "center" }}>Dynamic Form </h2>
<div style={{ textAlign: "center" }}>
<button
onClick={() => {
setPeople((currentPeople) => [
...currentPeople,
{
id: generate(),
firstName: "",
lastName: "",
},
]);
}}
>
add new person
</button>
{people.map((p, index) => {
return (
<div key={p.id}>
<input
placeholder="first name"
value={p.firstName}
onChange={(e) => {
const firstName = e.target.value;
setPeople((
currentPeople
) =>
currentPeople.map((x) =>
x.id === p.id ? { ...x, firstName } : x
)
);
}}
/>
<input
placeholder="last name"
value={p.lastName}
onChange={(e) => {
const lastName = e.target.value;
setPeople((currentPeople) =>
currentPeople.map((x) =>
x.id === p.id ? { ...x,lastName } : x
)
);
}}
/>
<button onClick={()=> setPeople( currentPeople =>currentPeople.filter(x=> x.id !== p.id) )}>x</button>
</div>
);
})}
<div>
<pre>{JSON.stringify(people, null, 3)}</pre>
</div>
</div>
</>
);
}
export default App;
No better explanation than the official one.
Here is the link: https://reactjs.org/docs/hooks-reference.html#usestate
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Your currentPeople is what the variable name suggests, the current value of the
const [people, setPeople] = useState<Person[]
For example:
You might send only one person that you want to add to your people's array, so you end up just attaching that Person to an already existing array of Persons.
Sure you could use setPeople([...people, newPerson]) but this wouldn't be correct in 100% of places because people might not have the latest value so the callback function comes to the rescue.
It is the current value of that state, this might come in handy, when you want to use the previous state to calculate the next state. An example would be a toggle function, that would toggle a modal or sth.
const [isOpen, setIsOpen] = useState(false);
const toggleIsOpen = () => setIsOpen(open=>!open);

Resources