Delete last item from array with useState - javascript

I'm new to JS, React and TypeScript. I did a tutorial to add a todo-list. To get some practice, I decided to add the delete button and the "remove last item" button.
Deleting the complete list worked out fine (I'm proud of myself, hah!) but the "delete last item" is not working, I tried different things (e.g. just todos.pop()).
function App() {
const [todos, setTodos] = useState([])
const [input, setInput] = useState("")
// prevents default, adds input to "todos" array and removes the input from form
const addTodo = (e) => {
e.preventDefault()
setTodos([...todos, input])
setInput("")
}
// deletes the todo-list
const clearTodo = (e) => {
e.preventDefault()
setTodos([])
}
// deletes the last entry from the todo-list
const clearLastTodo = (e) => {
e.preventDefault()
setTodos(todos.pop())
}
return (
<div className="App">
<h1>ToDo Liste</h1>
<form>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
type="text"
/>
<button type="submit" onClick={addTodo}>
Hinzufügen
</button>
</form>
<div>
<h2>Bisherige ToDo Liste:</h2>
<ul>
{todos.map((todo) => (
<li>{todo}</li>
))}
</ul>
</div>
<div>
<form action="submit">
<button type="submit" onClick={clearLastTodo}>
Letzten Eintrag löschen
</button>
<button type="submit" onClick={clearTodo}>
Liste löschen
</button>
</form>
</div>
</div>
);
}
export default App;
Am I missing something (clearly I am, otherwise it would work)? But what? :D
Thank you in advance!

There are 3 possible solutions to your problem.
Solution 1:
Slice the array from the first element to the -1 (1 before the last) element.
setTodos(todos.slice(0, -1)));
Or
setTodos((previousArr) => (previousArr.slice(0, -1)));
Solution 2:
Create a copy array and splice it with the value of -1. Then set the array from the first element to the -1 element.
const copyArr = [...todos];
copyArr.splice(-1);
setTodos(copyArr);
Solution 3:
Create a copy list with the ..., pop the copy and set the new value of the array to the copy.
const copyArr = [...todos];
copyArr.pop();
setTodos(copyArr);

You could do it as below. The spread operator makes sure you don't give the same reference to setTodos:
const clearLastTodo = (e) => {
e.preventDefault();
let copy = [...todos]; // makes sure you don't give the same reference to setTodos
copy.pop()
setTodos(copy);
}
A note about state in React:
Always treat state variables as if they were immutable. It's obvious for for primitive values like Number, Boolean... But for Objects and Arrays changing their content doesn't make them different, it should a new memory reference.

There are couple way to remove the last item from an array in javascript
const arr = [1, 2, 3, 4, 5]
arr.splice(-1) // -> arr = [1,2,3,4]
// or
arr.pop() // -> arr = [1,2,3,4]
// or
const [last, ...rest] = arr.reverse()
const removedLast = rest.reverse()
Following the guide updating objects and arrays in state
const onRemoveLastTodo = () => {
// Try with each way above
setTodos(todos.splice(-1))
}

Related

Remove element from useState array by index

SOLUTION: Update the key value for the input element to refresh the default value => content of the input element. Deleting an element from the array DID work. Thanks for your help!
src: https://thewebdev.info/2022/05/12/how-to-fix-react-input-defaultvalue-doesnt-update-with-state-with-javascript/#:~:text=state%20with%20JavaScript%3F-,To%20fix%20React%20input%20defaultValue%20doesn't%20update%20with%20state,default%20value%20of%20the%20input.
I got an useState array in my code which represents a lisst of students:
const [students, setStudents] = useState([""]);
This array gets mapped to student elements:
{students.map((student, index) => <Student setStudents={setStudents} students={students} id={index} key={index} content={student} />)} I also got an AddStudent element which adds students to the array.
function AddStudent(props) {
const {setStudents} = props;
return (
<button className="change-student add-student" onClick={() => {
setStudents((students) => [...students, ""])
}}>
+
</button>
);
}
The RemoveStudent component is supposed to remove a student by its index in the array. I've tried many different ways but none worked correctly. How can I get it to work? Here is my code:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button className="change-student remove-student" onClick={() => {
let data = students;
if(id > -1) {
data.splice(id, 1);
}
console.log(data)
// setStudents(data)
// alternative:
// setStudents(students.filter(index => index !== id)); // removes the last element in the list
// doesn't work properly
}}>
-
</button>
)
}
Thanks for your help!
2 things should be noted here:
While updating react state arrays, use methods that return a new array (map, filter, slice, concat),
rather than ones that modify the existing array (splice, push, pop, sort).
While updating React state using its previous value, the callback argument should be used for the state setter. Otherwise you may get stale values. (See React docs).
if(id > -1) {
setStudents(students=> students.filter((s,i)=>(i != id)))
}
Consult this article, for a complete reference about how to update React state arrays.
You need to copy the students array first and then try removing the student by index. I assume by id you mean index at which to remove the student. Then you can try something like:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button
className="change-student remove-student"
onClick={() => {
if(id > -1) {
const data = [...students]; // making a copy
data.splice(id, 1); // removing at index id
console.log(data)
setStudents(data)
}
}}
>
-
</button>
)
}
With array.filter() you have a mistake in how you pass callback to filter() method. Please try the following:
setStudents(students.filter((,index) => index !== id));
Notice the index is second param of the callback so I used a , before index.
After #Irfanullah Jan 's answer you should make sure how you show the student.
Here is the simple example:
const [students, setStudents] = useState([1, 2, 3]);
return (
<div>
{students.map((student, index) => {
return <div>{student}</div>; // show the value not the index
})}
<button
onClick={() => {
let id = 1;
const copy = [...students];
copy.splice(id, 1)
console.log(copy)
setStudents(copy);
}}
>
-
</button>
</div>
);
The code above will delete the student of "index==1"

Preventing The Entire List Re-Render on Item Addition in React Without Using memo

Please consider the following code: https://codepen.io/kyxey/pen/XWEWBRY
const { useRef, useState } = React;
function App() {
const inputRef = useRef(null);
const [jobs, setJobs] = useState([]);
const addJob = () => {
const newJob = inputRef.current.value;
if (newJob) {
setJobs((prevJobs) => [...prevJobs, newJob]);
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={addJob}>Add</button>
<ul>
{jobs.map((job) => {
const listItem = job + " " + Math.random();
return <li key={listItem}>{listItem}</li>;
})}
</ul>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
In this example, Whenever a new item gets added to the list, the entire list re-renders. You can tell this by looking at the random number in front of each item which will change with every newly items that are added to the list.
Now I know there are some duplications of this very question which I read them all. But none of them could solve my issue completely.
My question is: How can I prevent the re-render of the entire list each time a new item is added to it, WITHOUT using memo and useMemo in any shapes and forms? Meaning that whenever a new item is added to the list, only the new item is rendered and the other items on the list remain completely untouched. Again, I'm not able to use memo or useMemo to solve this.
For example:
Current behavior is like this:
I type Test in the input
Click the Add button
A new item gets added to the list below with a random number in front of it:
• Test 0.8025874545033296
I type AnotherTest in the input
Click the Add button
A new item gets added to the list below with a random number in front of it, BUT the random number in the front of the first item is also modified:
• Test 0.4454662757698613
• AnotherTest 0.16319305763152014
Expected behavior should be like this:
I type Test in the input
Click the Add button
A new item gets added to the list below with a random number in front of it:
• Test 0.8025874545033296
I type AnotherTest in the input
Click the Add button
A new item gets added to the list below with a random number in front of it, AND the random number in the front of the first item is NOT modified. Meaning that it's NOT re-rendered:
• Test 0.8025874545033296
• AnotherTest 0.16319305763152014
UPDATE:
This question was asked from me in a coding interview. They explicitly mentioned that I'm not allowed to use memo or useMemo in the sense that these are considered cheating! Now I don't know exactly why they think in such way but I'm sure there's an specific answer in their mind that is not how React is supposed to behave.
You've said:
This question was asked from me in a coding interview. They explicitly mentioned that I'm not allowed to use memo or useMemo in the sense that these are considered cheating! Now I don't know exactly why they think in such way but I'm sure there's an specific answer in their mind that is not how React is supposed to behave.
A reasonable answer to that — quite possibly the one they were expecting — is something along the lines of: "You can do that, but it would just be reinventing memo for no good reason, and by doing something so non-standard and unusual, it would make the code hard to understand and maintain for the next person." If they actually wanted to see a solution without using those things, I would suggest you cross them off the list of places you might consider working if you have any choice (I respect the fact you may not have a choice; I remember that vividly early in my career). That's a terrible interview question if they really wanted anything other than pushback (arguably even if they were looking for pushback), which can be indicative of a bad place to work.
But again, technically, you can it with a ref by storing the rendered li elements in it (perhaps in a Map). To me, that's much more "cheating" than doing it with memo as you should, but... Here's an example:
const { useRef, useState } = React;
// *** A means of having unique keys for jobs
let nextJobId = 1;
function App() {
const inputRef = useRef(null);
const [jobs, setJobs] = useState([]);
const renderedJobs = useRef(new Map());
const jobLiElements = renderedJobs.current;
const addJob = () => {
// *** Make the jobs objects, not just strings, so the
// same string can be used by more than one job and
// so we can give the job a unique ID.
const newJob = {
id: nextJobId++,
value: inputRef.current.value,
};
if (newJob) {
setJobs((prevJobs) => [...prevJobs, newJob]);
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={addJob}>Add</button>
<ul>
{jobs.map((job) => {
// *** Reuse li elements you already have if you have them,
// adding new ones if you don't
let li = jobLiElements.get(job);
if (!li) {
const listItem = job.value + " " + Math.random();
console.log(`Rendering li for ${job.value}`);
// *** You can't use `listItem` as the key, since there's
// _some_ chance of the same `li` having the same text and
// random number. Use the job object instead.
li = <li key={job.id}>{listItem}</li>;
jobLiElements.set(job, li);
}
return li;
})}
</ul>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
But even if I went so far as to show them that, I'd also show them a solution using memo and putting the random value in the state of the job, talking about the strengths of doing it that way (not least that it's the normal, expected way to do this):
const { useRef, useState } = React;
const JobItem = React.memo(({ job: { value, rand } }) => {
const text = `${value} ${rand}`;
console.log(`Rendering JobItem for "${text}"`);
return <li>{text}</li>;
});
// *** A means of having unique keys for jobs
let nextJobId = 1;
function App() {
const inputRef = useRef(null);
const [jobs, setJobs] = useState([]);
const addJob = () => {
// *** Make the jobs objects, not just strings, so the
// same string can be used by more than one job, and so
// we can assign it a unique ID to use as a key.
// *** Assign the random number once, as part of the job.
const newJob = {
id: nextJobId++,
value: inputRef.current.value,
rand: Math.random(),
};
if (newJob) {
setJobs((prevJobs) => [...prevJobs, newJob]);
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={addJob}>Add</button>
<ul>
{/* *** Now just use JobItem*/}
{jobs.map((job) => (
<JobItem key={job.id} job={job} />
))}
</ul>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

React useState, setState and {state} in return

I come across the rendering issue with React State.
The problem is that {state} in return get value one beat late.
But the console log in handleChange shows right value.
If the previous value of state is 9, current value of state value is 10 then the console.log({state}) in handleChange shows 10 and the <span>{state}<span> in return shows 9.
It looks different from other state async problem.
I can't understand why this happened.
const [findText, setFindText] = useState("");
const [findCount, setFindCount] = useState(0);
const handleChange = (e) => {
let str = e.target.value;
setFindText(str);
let cnt = 0;
doxDocument.map((docx) => {
cnt += docx.src.split(findText).length - 1;
});
setFindCount(cnt);
console.log({findCount})
};
return(
<div>
<input
type="text"
value={findText}
onChange={handleChange}
/>
<span>{findCount} found <span>
</div>
);
Two problems...
findText will not have been updated to the new value when you use it in split(). Either use str instead or calculate findCount in a memo or effect hook with a dependency on findText.
You're completely misusing filter(). Use reduce() to calculate a computed sum
const [findText, setFindText] = useState("");
const findCount = useMemo(
() =>
findText
? doxDocument.reduce(
(sum, { src }) => sum + src.split(findText).length - 1,
0
)
: 0,
[findText, doxDocument] // may not need doxDocument
);
return (
<div id="App">
<input
type="text"
value={findText}
onChange={(e) => setFindText(e.target.value)}
/>
<span>{findCount} found</span>
</div>
);

Is there a way to delete components from an array with a button click?

Still pretty new to react, so sorry if this question seems very simple.
I'm currently having issues where I have a component that I am attempting to add in each time the add button is clicked (which works) but when I press the delete button, it deletes all of the components in the array except the first one which cannot seem to be deleted. I am using .splice() to attempt to remove the last element in the array (-1) and setting the new state to reflect that but everything I do doesn't seem to fix anything. Any input would be greatly appreciated!
function App() {
const [inputList, setInputList] = useState([]);
const [disabled, setDisabled] = useState(false);
const onAddBtnClick = event => {
if (inputList.length < 5){
setInputList(inputList.concat(<Autocomplete items={universities} />));
}else{
setDisabled(true);
}
};
const onDeleteBtnClick = event => {
setInputList(inputList.splice(-1, 1));
if (inputList.length < 5){
setDisabled(false);
}
};
return (
<Fragment>
<div className="autocompleter">
<Button onClick={onAddBtnClick} disabled={disabled} size="lg" block>Add Education (Maximum 5)</Button>
<Button onClick={onDeleteBtnClick} size="lg" block>Delete Education</Button>
{inputList}
</div>
</Fragment>
);
}
If you want to delete the last element use Array.splice(0,array.length-1). This will remove your last element. Hope this helps. In your case use this block of code.
setInputList(prev=>prev.splice(0,prev.length-1));
Your problem is that splice doesn't return a new object, it returns the deleted elements. So you are setting the wrong new state.
Take a look here, you could use slice instead

Compare two array's and disable a single element if the id's are equal

I have two array in the state and both have id's.
if some array have the same value (In this case 8) I would like to disable all the buttons that have this equal value.
The buttons already exist, I just want to disable the ones that have the same non-unique ids.
I tried like this but i'm not getting it
var setOne = [2,6,8];
var setTwo = [3, 8, 4]
const button = () => {
var hasDuplicateValues = [...new Set(setOne)].filter(item => setTwo.includes(item));
if(hasDuplicateValues.length > 0) {
<button disabled />
}
else {
<button />
}
}
render(){
this.button()
}
This solution is disabling all the buttons but i want to disable the one with the same id only.
Thanks
It's not quite clear where in the app hierarchy that component is so I've attempted a bit of guess work. You're almost there by the looks of things. You just need to iterate over the buttons and create them.
function Button(props) {
const { disabled, text } = props;
return <button disabled={disabled}>{text}</button>;
}
// Buttons creates the buttons in the set
function Buttons() {
const setOne = [2, 6, 8];
const setTwo = [3, 8, 4];
// Remove the duplicates from the combined sets
const combined = [...new Set([...setOne, ...setTwo])];
// Get your duplicate values
const hasDuplicateValues = setOne.filter(item => setTwo.includes(item));
// `map` over the combined buttons
// If `hasDuplicateValues` includes the current button, disable it
return combined.map((n) => (
<Button
text={n}
disabled={hasDuplicateValues.includes(n) && 'disabled'}
/>
));
}
ReactDOM.render(<Buttons />, document.querySelector("#root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"/>

Categories

Resources