essentially I have a KanbanBoard-ish app I'm trying to develop and I'm getting some strange behavior when I call my delete function from my validation function. The code is here on codesandbox. The main issue is that when there are multiple cards and I try to delete a card with an onBlur event, the card where the event occurs is not deleted but another empty card is. It works as expected if all other cards in a column have text. Please ignore the dnd code, as it came after the original problem.
Thanks in advance.
EDIT:
Here is the logic with App.js
state = { list: list }
handleChange = (e, col) => {
let eid = parseInt(e.target.id)
let updatedList = this.state.list.map(obj => {
if (obj.id === col) {
let card = { text: e.target.value }
obj.cards[eid] = card
}
return obj
})
this.setState({ list: updatedList })
}
setText = (e, col) => {
if (e.target.value === "") {
e.target.placeholder = "YOU MUST ENTER TEXT. THIS BOX WILL CLOSE NOW"
e.persist()
setTimeout(() => {
this.delete(e, col)
}, 3000)
return
}
}
delete = (e, col) => {
e.preventDefault()
let eid = parseInt(e.target.id)
let updatedList = this.state.list.map(obj => {
if (obj.id === col) {
obj.cards = obj.cards.filter((c,i) => i !== eid)
//obj.counter--
}
return obj
})
this.setState({ list: updatedList })
}
add = e => {
e.preventDefault()
let eid = parseInt(e.target.id)
let updatedList = this.state.list.map(obj => {
if (obj.id === eid) {
let card = {text:""}
obj.cards.push(card)
obj.counter++
}
return obj
})
this.setState({ list: updatedList })
}
}
map returns an item for each item it iterates through. Maybe using filter would help in the case. I'm assuming that your splice is making the order of this.state.list get crazy and confusing.
let updatedList = this.state.list.filter(obj => obj.id !== col);
Not sure if col or eid is the correct thing to compare to, but that will get you a new list with all of the previous items except for the one whose id matches the id you're trying to delete.
Glancing at your codesandbox, there are some issues. To boil it down to a high level - each card should have an immutable ID, that you can use to delete it. You're using the index of the card in an array and combined with who knows what else. You've lost your source of truth, which is extra important when you are allowing the user to alter the order of an array. Your card should fire the delete function you pass it from its parent. It should just take the id of that card, filter that out of the current state, and set the new state. You're making this overcomplicated.
Parent -
state = { list : [{id: 1, ...other card stuff}, {...more cards}] };
delete = id => {
const newList = this.state.list.filter(item => item.id !== id);
this.setState({ list: newList };
}
render = () => {
const { list } = this.state;
return list.map(item => (
<Card
{...item}
onDelete={this.delete}
/>
))
}
Card -
// whenever you need to delete this card
this.props.onDelete(this.id);
Related
I have a todo app in JS with the following functions:
This is part of a function that passes an id into an event listener to remove a todo
removeButton.addEventListener('click', function () {
removeTodo(todo.id)
renderTodos(todos, filters)
})
This function removes the todo - I've used 2 approaches, the findIndex way works great, it removes the todo and renders the new todos fine - I thought the filter approach I've commented would also work but it doesn't, it does remove the todo but it doesn't automatically update the list in the browser unless I refresh the page, while splice does it automatically, why could this happen? could it be waiting to update local storage before renderTodos starts reading the list? Just a note that in the example that didn't work I was passing newTodos into the save function, I just changed it to todos for the splice way.
const removeTodo = function (id) {
const todoIndex = todos.findIndex(function (todo) {
return todo.id === id
})
if (todoIndex > -1) {
todos.splice(todoIndex, 1)
}
// newTodos = todos.filter(function (todo) {
// return todo.id !== id
// })
saveTodos(todos)
}
the todo list is saved in local storage
const saveTodos = function (todos) {
localStorage.setItem('todos', JSON.stringify(todos))
}
Here is the render function for information
const renderTodos = function (todos, filters) {
const filteredTodos = todos.filter(function (todo) {
const searchTextMatch = todo.text.toLowerCase().includes(filters.searchText)
const hideCompletedMatch = !filters.hideCompleted || !todo.completed
return searchTextMatch && hideCompletedMatch
})
const todosLeft = filteredTodos.filter(function (todo) {
return !todo.completed
})
document.querySelector('#todos').innerHTML = ''
document.querySelector('#todos').appendChild(generateSummaryDom(todosLeft))
filteredTodos.forEach(function (todo) {
document.querySelector('#todos').appendChild(generateTodoDom(todo))
})
}
splice() mutates the todos array which you are then renderering, while filter() returns a new array which you are not making use of.
To make it work with filter() you will need to return the newTodos from the remove function and render the returned array, not the original todos array.
removeButton.addEventListener('click', function () {
const newTodos = removeTodo(todo.id);
saveTodos(newTodos)
renderTodos(newTodos, filters);
})
const removeTodo = function (id) {
return todos.filter(todo => todo.id !== id)
}
const saveTodos = function (todos) {
localStorage.setItem('todos', JSON.stringify(todos))
}
Reassigning a variable does not have side-effects; reassigning one identifier has no effect on identifiers elsewhere. Doing
newTodos = todos.filter(function (todo) {
return todo.id !== id
})
saveTodos(todos)
}
means that you've put some results into newTodos without doing anything else with it. It doesn't get put into storage (or rendered, though how you render isn't shown).
Pass along the new filtered todos, and render (however you're doing it) from there - and don't forget to declare your variables.
const newTodos = todos.filter(function (todo) {
return todo.id !== id
})
saveTodos(newTodos);
renderTodos(newTodos);
while taking renderTodos out of the immediate listener callback.
I have a list of check inputs, and when they are selected and deselected I am trying to add/remove them from the state. But Ive noticed when I deselect one the one I selected prior is the object removed from the state. I think this is because when the onChange function is called the state hasnt updated (although the UI has) but I dont understand how to have a full list of all the checks selected with the state lagging behind one value. Heres my input and onChange func:
const [selected, setSelected] = useState([]);
const handleSelect = (e) =>
!!DATA &&
DATA.forEach((item, idx) => {
let name = e.target.name;
if (item.payerName === name && !selected.includes(item)) {
setSelected([...selected, item]);
return;
} else if (item.payerName === name && selected.includes(item)) {
// index varies
let index = selected.indexOf(item);
let clean = selected.splice(index, 1);
setSelected(clean);
}
});
DATA.map((item, idx) => {
return(
<input
name={item.payerName}
type="checkbox"
checked={selected.includes(item)}
onChange={handleSelect}
className="insurance-checkbox m-2 mt-3"
/>
)
}
Try this:
const [selected, setSelected] = useState([]);
const handleSelect = (e) => {
const { name } = e.target;
const item = DATA.find(({ payerName }) => payerName === name);
if (selected.includes(item)) {
setSelected(selected.filter((s) => s === item);
} else {
setSelected([...selected, item]);
}
}
or even better:
const handleSelect = (e) => {
const { name, checked } = e.target;
const item = DATA.find(({ payerName }) => payerName === name);
if (checked) {
if (!selected.includes(item)) { // probably not needed
setSelected([...selected, item]);
}
} else {
setSelected(selected.filter((s) => s === item);
}
}
I'm having a following situation where I want to know the indexOf value to be able to use that knowledge later on in the code. How ever, I've tried multiple different ways to get the value and I don't seem to get it right. In the code below I've tried a few different ways that I found searching Stackoverflow. All of them return -1 so far, meaning that there is either something wrong with my code or some other issue I'm not able to find at the moment.
FYI, selectedGroup is an array with objects inside, just like this:
[{label: "somelabel", value: 100}]
and there can be many of them, depends on the user.
code:
import React, { useState, useEffect } from 'react';
const GroupButtonMaker = ({ selectedGroup}) => {
const [newButtons, setNewButtons] = useState([]);
console.log(newButtons);
useEffect(() => {
const createButtons = () => {
setNewButtons(
selectedGroup.map(item => {
return (
<button
id={item.value}
className={'btn green micro'}
key={item.value}
onClick={event => btnHandler(event)}
>
{item.label}
</button>
);
})
);
};
createButtons();
}, [selectedGroup]);
const btnHandler = event => {
//here at the handler I'm trying multiple different ways to find indexOf as a test. No luck so far.
const eventID = event.currentTarget.id;
let currentTargetIndex = newButtons
.map(item => item.value)
.indexOf(eventID);
console.log(currentTargetIndex);
console.log(newButtons.findIndex(x => x.value === eventID));
};
Array.prototype.indexOfObject = function arrayObjectIndexOf(property, value) {
for (var i = 0, len = this.length; i < len; i++) {
if (this[i][property] === value) return i;
}
return -1;
};
// here i've hardcored one value as a test to see if it works but it didn't so far.
console.log(newButtons.indexOfObject('value', 107));
const idx = newButtons.reduce(function(cur, val, index, eventID) {
if (val.value === eventID && cur === -1) {
return index;
}
return cur;
}, -1);
console.log(idx);
return <ul>{newButtons.map(item => item)}</ul>;
};
export default GroupButtonMaker;
Thank you beforehand for any suggestions to my current problem. Hopefully I've managed to describe the problem in a way that makes it solveable. If not, please ask and I'll try to provide an answer.
Why not simply pass the id of the button to the handler instead of getting it from event.
You can achieve it by this: onClick={(event) => btnHandler(item.value)}
And then in your btnHandler, just look up the index of the selected button from selectedGroup instead of newButtons.
Here, give this a try:
import React, { useState, useEffect } from "react";
const GroupButtonMaker = ({ selectedGroup }) => {
const [newButtons, setNewButtons] = useState([]);
useEffect(() => {
const buttons = selectedGroup.map(item => {
return (
<button
id={item.value}
className={"btn green micro"}
key={item.value}
onClick={(event) => btnHandler(item.value)}
>
{item.label}
</button>
);
});
setNewButtons(buttons);
}, [selectedGroup]);
const btnHandler = buttonId => {
const selectedButtonIndex = selectedGroup.findIndex(item => item.value === buttonId);
console.log("selectedButtonIndex is: ", selectedButtonIndex);
};
return <ul>{newButtons.map(item => item)}</ul>;
};
export default GroupButtonMaker;
Here's a Working Sample Code Demo for your ref.
Making a comment section for a website and I ran into a big problem. Currently I have a delete button that splices the comments from state based on their index. I need to show the array in reverse to the user--so when they make multiple comments the newest one is ontop.
The problem is if I reverse() the mapped array the index doesn't get reversed with it, so clicking delete for item 1 deletes the last item, and vice versa.
const [userComments, setUserComments] = useState([])
const postComment = (event, userComment) => {
if (event.key === 'Enter') {
setUserComments(prevState => ([...prevState, {comment: userComment}]))
}
}
const deleteComment = (e, i) => {
const userCommentsArray = [...userComments]
userCommentsArray.splice(i, 1)
setUserComments(prevState => ([...prevState], userCommentsArray))
}
return (
<input
placeholder="Add a public comment"
onKeyUp={event => postComment(event, event.currentTarget.value)}
onClick={event => showCommentButtons()}
/>
{ userComments
? userComments.map((item, i) => (
<div className="button" onClick={e => deleteComment(e, i)}>Button</div>
<p className="comment">{item.comment}</p>
))
: null
}
)
Use reverse method on array:
const deleteComment = (e, i) => {
const userCommentsArray = [...userComments].reverse()
userCommentsArray.splice(i, 1)
setUserComments(prevState => ([...prevState], userCommentsArray.reverse()))
}
Figured it out. Used unshift to push the items to state in reverse order.
No other changes necessary.
const postComment = (userComment) => {
const userCommentNotBlank = !userComment.trim().length < 1
if (userCommentNotBlank) {
const newState = [...userComments]
newState.unshift(userComment)
setUserComments(prevState => ([...prevState], newState))
resetAddComment()
}
}
Each movie is only supposed to be added to the favourites list once but this bug makes it so that every time I change the page and go back to that certain movie page and I add it, it pushes itself into the favourites array. How do I check if a certain imdbID is already there and prevent the app from adding it into the state?
updateFavorites = movie => {
if (!this.state.favorites.includes(movie)) {
const newFavoriteList = this.state.favorites;
newFavoriteList.push(movie);
this.setState(state => ({
favorites: newFavoriteList
}));
console.log("Added to your favorites.");
} else {
const newFavoriteList = this.state.favorites;
newFavoriteList.splice(newFavoriteList.indexOf(movie), 1);
this.setState(state => ({
favorites: newFavoriteList
}));
console.log("Removed from your favorites.");
}
};
This is how I add and remove my movies from the favourites list.
As told in the comments you can use .find or .findIndex for this comparison. Also, you can use .filter instead of .splice and .indexOf to remove a movie. Personally I prefer this method.
updateFavorites = ( movie ) => {
const { favorites } = this.state;
const checkMovie = favorites.find( fav => fav.imdbID === movie.imdbID );
if (!checkMovie) {
return this.setState(
prevState => ( { favorites: [ ...prevState.favorites, movie ] } ) );
}
const newFavoriteList = favorites.filter( fav => fav.imdbID !== movie.imdbID );
this.setState( { favorites: newFavoriteList } );
};
Try this, It worked for me
let checkArray = favorites.every( (item) => {
return item.imdbID !== newItem.imdbID; // newItem is new element for insert
})
if(checkArray) {
//code for insert newItem
}