I'm facing a problem when I try to delete an item from an array in react state, only one element remains in the array containing all the other item's contents.
Here is a sandbox describing the problem.
Has anyone an idea for that behaviour ?
App.js
import React, { useEffect, useState } from "react";
import { Button, Input } from "semantic-ui-react";
import "./App.css";
import Item from "./components/Item";
function App() {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const [emptyError, setEmptyError] = useState(false);
const handleClick = () => {
if (todo.length === 0) {
setEmptyError(true);
} else {
todos.push(todo);
setTodos(todos);
setTodo("");
}
};
const handleChange = (e) => {
setTodo(e.target.value);
if (e.target.value.length > 0) {
setEmptyError(false);
}
};
const handleDelete = (e) => {
e.stopPropagation();
console.log(e.target.parentNode.id)
setTodos(todos.filter((item, index) => index !== +e.target.parentNode.id));
};
return (
<div className="App">
<div className="inputArea">
{" "}
<input
class="form-control"
onChange={handleChange}
value={todo}
type="text"
placeholder="Enter todo"
></input>{" "}
<button onClick={handleClick} type="button" class="btn btn-dark">
Add todo
</button>
</div>
{!emptyError ? (
""
) : (
<div className="error"> Todo can't be empty, please add a todo </div>
)}
<div className="todoList">
{todos.map((item, index) => {
return <Item text={item} delete={handleDelete} id={index} />;
})}
</div>
</div>
);
}
export default App;
You need to remove bracket in setTodos.
id of the element is a string. It has to be converted to number if you compare them with ===.
setTodos([todos.filter((item, index) => index !== e.target.parentNode.id)]);
to be:
setTodos(todos.filter((item, index) => index !== +e.target.parentNode.id));
you need convert string to number
you can do it with the help of the Number constructor
setTodos( todos.filter((item, index) => index!==Number(e.target.parentNode.id))
Related
import React, { useState, useEffect } from "react";
import "./style.css";
const getLocalItem = () => {
let list = localStorage.getItem("lists");
console.log(list);
if (list) {
return JSON.parse(list);
} else {
return [];
}
};
function App() {
const [text, setText] = useState("");
const [task, setTask] = useState(getLocalItem());
const changeText = (e) => {
setText(e.target.value);
};
const submitHandler = (e) => {
console.log("submited");
e.preventDefault();
setTask([...task, text]);
setText("");
};
const removeTask = (a) => {
const finalData = task.filter((curEle, index) => {
return index !== a;
});
setTask(finalData);
};
useEffect(() => {
localStorage.setItem("lists", JSON.stringify(task));
}, [task]);
return (
<>
<form onSubmit={submitHandler} className='form'>
<div className="action" >
<div >
<input
className="input"
type="text"
value={text}
onChange={changeText}
placeholder='add task...'
/>
</div>
<button type="submit" className="button" >
Add todo
</button>
</div>
<div className="listsData">
{task.map((value, index) => {
return (
<>
<div key={index}>
{value}
</div>
</>
);
})}
</div>
</form>
</>
);
}
export default App;
On adding each item I want a different color for each list. Currently, I am fetching list data from localstorage while fetching also it should remain same. which is working but the dynamic colors is what I need for each list. Any ideas or dynamic logics??
Let me know if u need more details regarding my code if u doont understand something
I am beginner and practicing on Library Management System in react. So I have components named BookDetails.js, BookList.js. BookDetails contains the form for entering Title and Description. So How can I pass the data entered from BookDetails to BookList and to dispaly from App.
import React, { useState } from 'react'
import BookList from './BookList'
const BookDetails = (props) => {
const [bookdetails, setbookDetails] = useState('')
const [desc, setDesc] = useState('')
const titleChangehandler = (e) => {
setbookDetails(e.target.value)
}
const descriptionChangehandler = (e) => {
setDesc(e.target.value)
}
const submitHandler = (e) => {
e.preventDefault()
return (
<div className='bookdetails'>
<form className='form_bookdetails' onSubmit={submitHandler}>
<div>
<label>Enter Title:</label>
<input type='text' value={bookdetails} onChange={titleChangehandler}></input>
</div>
<div>
<label>Enter Description:</label>
<input type='text' value={desc} onChange={descriptionChangehandler}></input>
</div>
<div>
<button type='submit'>Add Details</button>
</div>
</form>
</div>
)
}
}
export default BookDetails
BookList.js
import React from 'react'
import './BookList.css'
import BookDetails from './BookDetails'
const BookList = () => {
return (
<div className="booklist">
<header>BookList</header>
<BookDetails />
</div>
)
}
export default BookList
You need to use props. BookList state will have an update function that it will pass to the BookDetail via props. Example (CodeSandbox) with Todo with title & description.
BookDetail will invoke this method on every save which then would update the original list.
TodoList.js
export default function TodoList() {
const [todo, setTodo] = React.useState(null);
const [todoList, setTodoList] = React.useState([]);
React.useEffect(() => {
getTodos();
}, []);
function getTodos() {
console.log("===> fetch all todos!!");
fetchTodos().then((todos) => {
setTodoList(todos);
});
}
function editTodo(todo) {
console.log("===> set todo => ", todo);
setTodo(todo);
}
function handleUpdate(updatedTodo) {
// update Todo
const updatedTodos = todoList.map((el) =>
el.id === updatedTodo.id ? updatedTodo : el
);
setTodoList(updatedTodos);
setTodo(null);
}
return (
<div>
<ul>
{todoList.map((item) => (
<li key={item.id}>
{item.title}, {item.description}
<button onClick={() => editTodo(item)}>edit</button>
</li>
))}
</ul>
{todo && <TodoDetail todo={todo} updateTodo={handleUpdate} />}
</div>
);
}
TodoDetail.js
import React from "react";
export default function TodoDetail(props) {
const [todo, setTodo] = React.useState(props.todo);
console.log("todo =>", todo);
function handleChange(key, value) {
console.log("===> todo changed!");
setTodo({
...todo,
[key]: value
});
}
function handleSubmit() {
// api PUT on todo
console.log("===> todo edit submit!!");
props.updateTodo(todo);
}
return (
<div>
<form onSubmit={handleSubmit}>
<label htmlFor="title">
<input
value={todo.title}
onChange={(e) => handleChange("title", e.target.value)}
/>
<input
value={todo.description}
onChange={(e) => handleChange("description", e.target.value)}
/>
</label>
<button type="submit">submit</button>
</form>
</div>
);
}
You can store the list of books in your BookList component like
const [bookList, setBookList] = useState([])
This way your BookList component has access to the books. You can then create a function to add books to the list
function addBook(book) {
setBookList([...bookList, book])
}
Then pass the addBook() function to the BookDetails component to use it on submit.
<BookDetails addBook={addBook}
Now BookDetails can access the function as a prop
props.addBook("pass new book here")
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;
Helllo everyone, I have this issue where I am successfully sorting the array state of an object alphabetically using their cities but the problem is that the array that is getting visualized only updates after I search something on the UI.
I tried to look it up but still lost here is the video of what is happening
https://drive.google.com/file/d/17pAwTeo8IZ6mw3dd2pxDxbfY-ToL7cjG/view?usp=sharing
here is the code
full code here
import React, { useState, useEffect } from "react";
import "./body.css";
import Axios from "axios";
import { Button } from "#material-ui/core";
function SearchBar() {
const [filteredData, setFilteredData] = useState([]);
const [search, setSearch] = useState("");
async function getUsers() {
Axios.get("https://jsonplaceholder.typicode.com/users")
.then((response) => {
setFilteredData(response.data);
})
.catch((err) => {
console.log(err);
});
}
useEffect(() => {
getUsers();
}, []);
function handleReset() {
getUsers();
setSearch("");
handleClear();
}
const handleClear = () => {
Array.from(document.querySelectorAll("input")).forEach(
(input) => (input.value = "")
);
};
const delItem = (id) => {
setFilteredData(filteredData.filter((e) => e.name.localeCompare(id) !== 0));
};
const sort = () => {
setFilteredData(
filteredData.sort((a, b) => {
return a.address.city.localeCompare(b.address.city);
})
);
// console.log(sorted);
// setFilteredData(sorted);
console.log(filteredData);
};
return (
<div>
<form class="search-bar">
<input
type="input"
name="search"
pattern=".*\S.*"
// requiredw
autoComplete="off"
placeholder="Input Text Here"
onChange={(e) => setSearch(e.target.value)}
></input>
</form>
<Button onClick={handleReset}>Reset</Button>
<Button onClick={sort}>Sort</Button>
<div>
<ul>
{filteredData
.filter((user) => {
var dynamicSearch = search;
if (
user.name.toLowerCase().includes(dynamicSearch.toLowerCase()) ||
user.email
.toLowerCase()
.includes(dynamicSearch.toLowerCase()) ||
user.phone
.toLowerCase()
.includes(dynamicSearch.toLowerCase()) ||
user.address.city
.toLowerCase()
.includes(dynamicSearch.toLowerCase())
) {
return true;
}
})
.map((val, index) => (
<li className="li" key={val.id}>
<p className="list">
{"Name: "}
{val.name} <br />
{"Email: "}
{val.email}
<br />
{"Phone: "}
{val.phone}
<br />
{"City: "}
{val.address.city}
</p>
<button className="delButton" onClick={() => delItem(val.name)}>
x
</button>
</li>
))}
</ul>
</div>
</div>
);
}
export default SearchBar;
Just try with this one:
const sort = () => {
const sortedData = filteredData.sort((a, b) => {
return a.address.city.localeCompare(b.address.city);
});
setFilteredData([...sortedData]);
};
Problem is once you are updating the sorting data in setFilteredData function its not able to observe the changes that needs to be rendered. So always make the copy of the state variables when you are updating the values.
I am trying to create a CRUD using React Hooks but I am having some problems on the updateItem function.
The first input using useState works perfectly (I can type inside the input) but when I click Rename Item, the second input appears but I can’t type inside and doesn’t log any errors from console.
Here is my code:
import ReactDOM from 'react-dom';
import React, { useState } from 'react';
function App() {
//List with one item
const [list, setList] = useState([{ id: Math.random() + 1, name: 'Test' }]);
//Inputs
const [input, setInput] = useState('');
const [newInput, setNewInput] = useState('');
const [edit, setEdit] = useState();
function createItem(value) {
if (!value.trim()) return;
let obj = { id: Math.random() + 1, name: value }
setList([...list, obj])
}
function deleteItem(id) {
setList(list.filter(item => item.id !== id));
}
function updateItem(id) {
setEdit(
<div>
//I can't type anything in here
<input type="text" value={newInput} onChange={e => setNewInput(e.target.value)} />
<button onClick={() => {
let array = [...list];
array.map((item, i) => {
if (item.id === id) array[i] = { id, newInput }
})
setList([...array])
setEdit('') //Remove the edit from the DOM
}}>Rename</button>
</div>
)
}
return (
<div>
<input type="text" value={input} onChange={e => setInput(e.target.value)} />
<button onClick={() => createItem(input)}>Add Item</button>
{list.map(item => (
<div key={item.id}>
<p>{item.name}</p>
<button onClick={() => updateItem(item.id)}>Rename Item</button>
<button onClick={() => deleteItem(item.id)}>Delete Item</button>
</div>
))}
{edit}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
Try to use setEdit to show/hide through boolean value. You will have ability to type in the input
import ReactDOM from "react-dom";
import React, { useState } from "react";
function App() {
//List with one item
const [list, setList] = useState([{ id: Math.random() + 1, name: "Test" }]);
//Inputs
const [input, setInput] = useState("");
const [newInput, setNewInput] = useState("");
const [edit, setEdit] = useState(false);
function createItem(value) {
if (!value.trim()) return;
let obj = { id: Math.random() + 1, name: value };
setList([...list, obj]);
}
function deleteItem(id) {
setList(list.filter(item => item.id !== id));
}
function updateItem() {
setEdit(true);
}
return (
<div>
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
/>
<button onClick={() => createItem(input)}>Add Item</button>
{list.map(item => (
<div key={item.id}>
<p>{item.name}</p>
<button onClick={() => updateItem(item.id)}>Rename Item</button>
<button onClick={() => deleteItem(item.id)}>Delete Item</button>
{edit && (
<div>
//I can't type anything in here
<input
type="text"
value={newInput}
onChange={e => {
setNewInput(e.target.value);
}}
/>
<button
onClick={() => {
let array = [...list];
array.map((o, i) => {
if (o.id === item.id) array[i] = { id: item.id, newInput };
});
setList([...array]);
setEdit(false); //Remove the edit from the DOM
}}
>
Rename
</button>
</div>
)}
</div>
))}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
The problem is with how you run updateItem() function.
You are using the following call <button onClick={() => updateItem(item.id)}>Rename Item</button>
In updateItem(), you do a setEdit and it adds an input box and button to your screen.
But now, you have trouble updating the input box. This is because the input box will only be updated when you run the updateItem() function again.
So in order for your input box to be updated, you would have to run setEdit again
Here is a solution of how to put it in your main component so that the input box updates with each render
import ReactDOM from "react-dom";
import React, { useState } from "react";
function App() {
//List with one item
const [list, setList] = useState([{ id: Math.random() + 1, name: "Test" }]);
//Inputs
const [input2, setInput2] = useState("");
const [newInput, setNewInput] = useState("");
const [edit, setEdit] = useState(null);
function createItem(value) {
if (!value.trim()) return;
let obj = { id: Math.random() + 1, name: value };
setList([...list, obj]);
}
function deleteItem(id) {
setList(list.filter((item) => item.id !== id));
}
function renameItem() {
// Rename item
alert("Rename item " + edit + " to " + newInput);
setEdit(null);
setNewInput(null);
}
return (
<div>
<input
type="text"
value={input2}
onChange={(e) => setInput2(e.target.value)}
/>
<button onClick={() => createItem(input2)}>Add Item</button>
{list.map((item) => (
<div key={item.id}>
<p>{item.name}</p>
<button onClick={() => setEdit(item.id)}>
Rename Item {item.id}
</button>
<button onClick={() => deleteItem(item.id)}>Delete Item</button>
</div>
))}
{edit && (
<div>
<input
type="text"
value={newInput}
onChange={(e) => setNewInput(e.target.value)}
/>
<button onClick={() => renameItem()}>Rename</button>
</div>
)}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
There are other better solutions, such as turning the {edit && ...} part to an component. This should get your code working for now.
I also left out the code to actually rename the item, you should be able to do that yourself also.