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.
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
As a Begineer in a react,i have just implementing a dynamic array field for learning but got a problem in delete operation of removing inputs fields from the row field with passing its id in deletefunction.How to overcome this problem?
Code
import React, { useState} from "react";
import "./styles.css";
const initialValues = [
{number: "",options: ""}
];
const Newrow = (props) => {
const [number, setNumber] = useState("");
const [options, setoption] = useState([]);
const addRow = () => {
let _row = {
number: "",
options: ""
};
props.setData(_row);
};
const delrow = (i) => {
data.splice(i,2)
setData({})
}
return <div>
<input
type="number"
value={number}
onChange={(e) => {
setNumber(e.target.value);
}}
/>
<input type="text"
className="input"
value={options}
onChange={e=>{setoption(e.target.value)}}
/>
<button
type="submit"
onClick={delrow}
className="btn btn-danger">remove</button>
</div>
};
export default function App(props) {
const [data, setData] = useState([]);
const addRow = (row) => {
setData([...data, row]);
};
return (
<div className="App">
{[...data, ...initialValues].map((row, idx) => {
return <Newrow setData={addRow} data={row} key={idx} delrow{idx} />;
})}
<button
type="submit"
onClick={() =>
addRow({number: "",options: "" })}
className="btn btn-success">Add</button>
</div>
);
}
i want to optimize my react App by testing with a large list of li
Its a simple todo List.
By exemple, when click on a li, task will be line-through, and check icon will be green. This simple action is very slow with a large list because, the whole list is re render.
How to do this with React Hooks?
function App() {
const [list, setList] = useState([]);
const [input, setInput] = useState("");
const inputRef = useRef(null);
useEffect(() => inputRef.current.focus(), []);
//Pseudo Big List
useEffect(() => {
const test = [];
let done = false;
for (let i = 0; i < 5000; i++) {
test.push({ task: i, done });
done = !done;
}
setList(test);
}, []);
const handlerSubmit = (e) => {
e.preventDefault();
const newTask = { task: input, done: false };
const copy = [...list, newTask];
setList(copy);
setInput("");
};
const checkHandler = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy[index].done = !copy[index].done;
setList(copy);
};
const suppression = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy.splice(index, 1);
setList(copy);
};
const DisplayList = () => {
return (
<ul>
{list.map((task, index) => (
<Li
key={index}
task={task}
index={index}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
);
};
//JSX
return (
<div className='App'>
<h1>TODO JS-REACT</h1>
<form id='form' onSubmit={handlerSubmit}>
<input
type='text'
placeholder='Add task'
required
onChange={(e) => setInput(e.target.value)}
value={input}
ref={inputRef}
/>
<button type='submit'>
<i className='fas fa-plus'></i>
</button>
</form>
{list.length === 0 && <div id='noTask'>No tasks...</div>}
<DisplayList />
</div>
);
}
export default App;
Li component
import React from "react";
export default function Li(props) {
return (
<li
onClick={(e) => props.checkHandler(e, props.index)}
className={props.task.done ? "line-through" : undefined}
>
{props.task.task}
<span className='actions'>
<i className={`fas fa-check-circle ${props.task.done && "green"}`}></i>
<i
className='fas fa-times'
onClick={(e) => props.suppression(e, props.index)}
></i>
</span>
</li>
);
}
CodeSandbox here: https://codesandbox.io/s/sad-babbage-kp3md?file=/src/App.js
I had the same question, as #Dvir Hazout answered, I followed this article and made your code the changes you need:
function App() {
const [list, setList] = useState([]);
const { register, handleSubmit, reset } = useForm();
//Pseudo Big List
useEffect(() => {
const arr = [];
let done = false;
for (let i = 0; i < 20; i++) {
arr.push({ id: uuidv4(), task: randomWords(), done });
done = !done;
}
setList(arr);
}, []);
const submit = ({ inputTask }) => {
const newTask = { task: inputTask, done: false, id: uuidv4() };
setList([newTask, ...list]);
reset(); //clear input
};
const checkHandler = useCallback((id) => {
setList((list) =>
list.map((li) => (li.id !== id ? li : { ...li, done: !li.done }))
);
}, []);
const suppression = useCallback((id) => {
setList((list) => list.filter((li) => li.id !== id));
}, []);
//JSX
return (
<div className="App">
<h1>TODO JS-REACT</h1>
<form onSubmit={handleSubmit(submit)}>
<input type="text" {...register("inputTask", { required: true })} />
<button type="submit">
<i className="fas fa-plus"></i>
</button>
</form>
{list.length === 0 && <div id="noTask">No tasks...</div>}
<ul>
{list.map((task, index) => (
<Li
key={task.id}
task={task}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
</div>
);
}
Li component
import React, { memo } from "react";
const Li = memo(({ task, suppression, checkHandler }) => {
// console.log each time a Li component re-rendered
console.log(`li ${task.id} rendered.`);
return (
<li
onClick={(e) => checkHandler(task.id)}
className={task.done ? "line-through" : undefined}
>
{task.task}
<span className="actions">
<i className={`fas fa-check-circle ${task.done && "green"}`}></i>
<i className="fas fa-times" onClick={(e) => suppression(task.id)}></i>
</span>
</li>
);
});
export default Li;
You can check it live here
I know it's probably late for your question, but may help others ;)
You can use React.memo and wrap the Li component. This will cache the instances of the Li component based on shallow comparison. Read more in the docs
Otherwise, if you don't need the state in the container, you can keep it locally in the Li component and then it won't cause a whole list rerender.
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'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))