I have a simple notes app and delete isn't working properly, even though state is correctly updated.
The state is being updated correctly as I can see on the console.
But all the notes including and after that note that I click delete on are getting deleted on the DOM for some reason.
For example if I have 3 notes ["hello","hi","hey"], if I delete the second note("hi"), the state shows the correct notes ["hello","hey"] but both "hi" and "hey" are deleted on the page not just "hi" like it was supposed to.
I can't understand where I've gone wrong, so I'd like to correct it.
App.js:
handleDelete = (note_id) => {
const id = 'display-' + note_id;
console.log(id);
document.getElementById(id).style.display = 'none';//remove element
//delete the note and update state
const newNotesList = this.state.notesList;
newNotesList.splice(note_id,1);
this.setState({
notesList : newNotesList
})
console.log(this.state.notesList)
}
Display.js:
render(){
const notesList = this.props.notesList;
const displayNotes = notesList.map( (note,note_id) =>
<div id={ 'display-' + note_id } className="display">
{/*some code*/}
<button type="button" className="delete-button"
onClick = { () => this.props.handleDelete(note_id) } > Delete </button>
</div> );
return <div>{displayNotes}</div>;
}
do like this
// don't mutation object
// App.js
handleDelete = (note_id) => {
//delete the note and update state
const newNotesList = this.state.notesList.filter((item, index)=> index !== note_id)
this.setState({
notesList : newNotesList
})
}
// Display.js
render(){
const notesList = this.props.notesList;
const displayNotes = notesList.map( (note,note_id) =>
<div>
{/*some code*/}
<button type="button" className="delete-button"
onClick = { () => this.props.handleDelete(note_id) } > Delete </button>
</div> );
return <div>{displayNotes}</div>;
}
==== here is the reason ========
at first the state.note is ["hello","hi","hey"], in the function of handleDelete you delete "hi" and make the id of dispaly-1's display become to hidden, so when react render the state.note === ["hello","hey"] the element of "hey"'s id become dispaly-1 so "hey" will be hidden. you will only see "hello"
handleDelete = (note_id) => {
// this.state.notesList === ["hello","hi","hey"]
const id = 'display-' + note_id;
console.log(id);
// the problem is u hidden the next element
// 1. newNotesList.splice(note_id,1);
// 2. document.getElementById(id).style.display = 'none'
// two methods choose one or you will hidden two elements
document.getElementById(id).style.display = 'none';//remove element
//delete the note and update state
const newNotesList = this.state.notesList;
newNotesList.splice(note_id,1);
this.setState({
notesList : newNotesList
})
console.log(this.state.notesList)
// for example
// the `this.state.notesList` new is ["hello","hey"]
}
notesList.map( (note,note_id) =>
// `note` is ["hello","hey"] , u don't need hidden the `2rd` element
//you have been delete 'hi' ` the id of `display-1`'s display ==='hidden'
// now "hey"'s id is `display-1`
<div id={ 'display-' + note_id } className="display">
{/*some code*/}
<button type="button" className="delete-button"
onClick = { () => this.props.handleDelete(note_id) } > Delete </button>
</div> );
``
Related
I have an app that stores items into locations in localStorage and then displays the items in HTML.
One of the reasons i wanted to use Svelte was for reactive variables, but whenever I attempt to use a reactive variable that changes whenever localStorage.current_items changes, the ItemList variable doesn't change.
The only way I could get it to work is by using setInterval but that is not a great way to do it. How can I make it so that ItemList changes properly when the localStorage.current_items string changes.
<script lang="ts">
import {
getData,
createItem,
createLocation,
closeItem,
} from './lib/database.js';
import LocationSelector from './lib/LocationSelector.svelte';
import { flip } from 'svelte/animate';
import { writable } from 'svelte/store';
let DB = getData();
// load items from localstorage.items
let ItemList = [];
let Location;
setInterval(() => {
Location = localStorage.current_items;
ItemList = JSON.parse(localStorage.current_items).items;
}, 500);
console.log(ItemList);
let newItem = '';
let filter_showClosed = false;
function addNewItem(e) {
e.preventDefault();
console.log(newItem);
const newItemInput = document.querySelector(
'#newItemInput'
) as HTMLInputElement;
createItem(JSON.parse(Location).id, newItem);
newItem = '';
}
function newItemKeyDown(e) {
if (e.keyCode === 13) {
addNewItem(e);
}
}
</script>
<LocationSelector />
<div class="app">
<input
type="text"
id="newItemInput"
bind:value={newItem}
placeholder="Add a new item"
on:keydown={newItemKeyDown}
/>
<button
id="filter_showClosed"
data-active="false"
on:click={function () {
filter_showClosed = !filter_showClosed;
let el = document.getElementById('filter_showClosed');
if (filter_showClosed) {
el.innerHTML = 'Hide closed';
el.dataset.active = 'true';
} else {
el.innerHTML = 'Show closed';
el.dataset.active = 'false';
}
}}>Show closed</button
>
<!-- <button
id="deleteClosed"
on:click={function () {
let it = items;
for (let i = 0; i < it.length; i++) {
if (it[i].closed == true) {
it.splice(i, 1);
}
}
items = it;
sort_items(items);
}}>Delete all closed</button
> -->
<div class="list">
{#each ItemList as item, index (item.id)}
<div class="item {item.closed}" animate:flip={{ duration: 100 }}>
{#if item.closed == false || (filter_showClosed == true && item.closed == true)}
<div>
<img
src="/up.svg"
class="item-icon"
class:closed={item.closed == true}
alt="move item up in priority"
on:click={function () {
// increaseLevel({ item });
}}
/>
{item.name} ({index})
</div>
<div>
{#if item.closed == false}
<img
src="/close.svg"
class="item-icon"
alt="close item"
on:click={function () {
console.log(Location.id);
closeItem(JSON.parse(Location).id, item.id);
}}
/>
{/if}
</div>
{/if}
</div>
{/each}
</div>
</div>
<style>
</style>
I tried using this writeable method, but that didn't work either as the variable still didn't change.
import { writable } from 'svelte/store';
const ItemList = writable([]);
let Location = {};
let newItem = '';
let filter_showClosed = false;
function addNewItem(e) {
e.preventDefault();
console.log(newItem);
const newItemInput = document.querySelector(
'#newItemInput'
) as HTMLInputElement;
createItem(Location.id, newItem);
newItem = '';
}
function newItemKeyDown(e) {
if (e.keyCode === 13) {
addNewItem(e);
}
}
// Update the Location object with the current value of localStorage.current_items as an object
Location = JSON.parse(localStorage.current_items);
// Update the ItemList store with the new location's items
ItemList.set(Location.items);
You should use a store that fully wraps the access to localStorage.
Something like:
function localStorageStore(key, initial) {
const value = localStorage.getItem(key)
const store = writable(value == null ? initial : JSON.parse(value));
store.subscribe(v => localStorage.setItem(key, JSON.stringify(v)));
return store;
}
Reading and writing is just a regular store, but on initial load the value comes from the storage and on setting the value, it is also written to storage.
const addToList = function ({ name, id }) {
let html1 = `
<li class="list-item" data-id="${id}" id="${id}">
${name}
<span> </span>
<button class="btn-remove">remove</button>
</li>
`;
pokemonList.insertAdjacentHTML('beforeend', html1);
};
const displayListItem = function () {
favourite.forEach((listItem) => {
addToList(listItem);
});
};
let favourite = getStorageItem('pokemon');
pokemonContainer.addEventListener('click', function (e) {
if (e.target.classList.contains('btn-favourites')) {
favourite = getStorageItem('pokemon');
let eTargetID = parseInt(e.target.dataset.id);
let found = favourite.some((el) => el.id === eTargetID);
console.log(found);
console.log(eTargetID);
if (!found) {
favourite.push(pokemonArray[e.target.dataset.id - 1]);
setStorageItem('pokemon', favourite);
// behöver lösa duplcates i favorit listan
displayListItem(favourite);
}
}
});
How do I check if a child element with a dataset id exists? I want to prevent duplicate from appearing in my favourite list when I get them from local storage when I click on a button, if a li tag already exists with a specific dataset id.
Right now when I click let's say bulbasaur, I get bulbasaur in my list. If i then click ivysaur, I get bulbasaur, bulbasaur, ivysaur.
Thanks!
You can use querySelector, that dynamically accepts an id to search, for example:
document.querySelector(`[data-id="${datasetId}"]`)
Returns the element if exists, if not returns null.
Why when you are searching for something else is deleting the previous contents ?For example first you search for egg and show the contents but then when you search for beef the program deletes the egg and shows only beef.Thank you for your time code:
const searchBtn = document.getElementById('search-btn');
const mealList = document.getElementById('meal');
const mealDetailsContent = document.querySelector('.meal-details-content');
const recipeCloseBtn = document.getElementById('recipe-close-btn');
// event listeners
searchBtn.addEventListener('click', getMealList);
mealList.addEventListener('click', getMealRecipe);
recipeCloseBtn.addEventListener('click', () => {
mealDetailsContent.parentElement.classList.remove('showRecipe');
});
// get meal list that matches with the ingredients
function getMealList(){
let searchInputTxt = document.getElementById('search-input').value.trim();
fetch(`https://www.themealdb.com/api/json/v1/1/filter.php?i=${searchInputTxt}`)
.then(response => response.json())
.then(data => {
let html = "";
if(data.meals){
data.meals.forEach(meal => {
html += `
<div class = "meal-item" data-id = "${meal.idMeal}">
<div class = "meal-img">
<img src = "${meal.strMealThumb}" alt = "food">
</div>
<div class = "meal-name">
<h3>${meal.strMeal}</h3>
Get Recipe
</div>
</div>
`;
});
mealList.classList.remove('notFound');
} else{
html = "Sorry, we didn't find any meal!";
mealList.classList.add('notFound');
}
mealList.innerHTML = html;
});
}
Beacuse you are using innerHTML , if you want to save the previous contents you should use append or innerHTML + = .
Because everytime you make a search, the html var is populated with new data.
if you move the 'html' variable to the root scope, this should get you there:
// get meal list that matches with the ingredients
let html = ""; // <-- place it outside the function body
function getMealList(){
let searchInputTxt = document.getElementById('search-input').value.trim();
fetch(`https://www.themealdb.com/api/json/v1/1/filter.php?i=${searchInputTxt}`)
.then(response => response.json())
.then(data => {
// let html = ""; // <-- remove it from here
if(data.meals){
data.meals.forEach(meal => {
I am doing a task list with an editable function for the each task item. What I expect is that when I update item's value, the value in LocalStorage update simultaneously. Currently, the value in LocalStorage can be updated, however, it only updates the last value of it no matter which item's value I modify. And the one should be changed does not be modified. How do I change correct localStorage value when I revise the task item?
const todo__input = document.querySelector(".todo__input")
const add__btn = document.querySelector(".add__btn")
const item__sector = document.querySelector(".item__sector")
function createToDoItem(toDoItem) {
const position = "beforeend"
const item = `
<div class="item">
<input type="checkbox" class="done__btn">
<input type="text" class="item__content" value="${toDoItem}" disabled>
<button class="edit__btn"><i class="far fa-edit"></i></button>
<button class="delete__btn"><i class="far fa-trash-alt"></i></button>
</div>
`
item__sector.insertAdjacentHTML(position, item)
return item__sector
}
// load todo item from localstorage when page is loaded
document.addEventListener("DOMContentLoaded", getLocalStorage)
// add item to the item sector
add__btn.addEventListener("click", e => {
e.preventDefault()
const input__value = todo__input.value
if (input__value.trim() === "") { return }
createToDoItem(input__value)
saveLocalStorage(input__value)
todo__input.value = ""
})
// keypress Enter
document.addEventListener("keypress", e => {
if (e.keyCode == 13) {
e.preventDefault()
const input__value = todo__input.value
if (input__value.trim() === "") { return }
createToDoItem(input__value)
saveLocalStorage(input__value)
todo__input.value = ""
}
})
// the function on item (done, edit, and delete)
item__sector.addEventListener("click", e => {
const parent = e.target.parentElement
// done
if (e.target.classList.contains("done__btn")) {
e.target.nextElementSibling.classList.toggle("done__color")
}
// edit the todo item
if (e.target.classList.contains("edit__btn")) {
if (e.target.previousElementSibling.disabled.disabled == true) {
e.target.previousElementSibling.disabled = !e.target.previousElementSibling.disabled
} else {
e.target.previousElementSibling.disabled = !e.target.previousElementSibling.disabled
e.target.previousElementSibling.setAttribute("value", e.target.previousElementSibling.value)
editLocalStorage(e.target.previousElementSibling)
}
}
// delete todo item
if (e.target.classList.contains("delete__btn")) {
parent.remove()
deleteLocalStorage(e.target.previousElementSibling.previousElementSibling)
}
})
// function for check todo status in the LocalStorage
function checkLocalStorage() {
let todos
if (localStorage.getItem("todos") === null) {
todos = []
} else {
todos = JSON.parse(localStorage.getItem("todos"))
}
return todos
}
// function for save localstorage
function saveLocalStorage(todo) {
const todos = checkLocalStorage()
todos.push(todo)
localStorage.setItem("todos", JSON.stringify(todos))
}
// function for get item and render to the screen from localstorage
function getLocalStorage() {
const todos = checkLocalStorage()
todos.forEach(todo => {
createToDoItem(todo)
})
}
// edit localStorage
function editLocalStorage(todo) {
const todos = checkLocalStorage()
const todoIndex = todo.getAttribute("value")
todos.splice(todos.indexOf(todoIndex), 1, todoIndex)
localStorage.setItem("todos", JSON.stringify(todos))
}
====
<body>
<div class="container">
<h1 class="title">My To-Do List</h1>
<form class="add__todo">
<input type="text" class="todo__input" placeholder="Add a task...">
<button class="add__btn">Add</button>
</form>
<div class="item__sector">
</div>
<div class="item__status">
<button class="all">All</button>
<button class="completed">COMPLETE</button>
<button class="incompleted">UNCOMPLETE</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/js/all.min.js"></script>
<script src="app.js"></script>
</body>
The reason that causes your solution to edit the last value is:-
The todoIndex variable inside the editLocalStorage function is referencing the new updated value from your input which is not yet stored inside the todos array in local storage therefore indexOf() returns -1 hence editing the last value.
I have rewritten the JS with a tweak to the functions item__sector.addEventListener, editLocalStorage and added a global variable edit__index
Code Snippet
const todo__input = document.querySelector(".todo__input")
const add__btn = document.querySelector(".add__btn")
const item__sector = document.querySelector(".item__sector")
let edit__index = -1
function createToDoItem(toDoItem) {
const position = "beforeend"
const item = `
<div class="item">
<input type="checkbox" class="done__btn">
<input type="text" class="item__content" value="${toDoItem}" disabled>
<button class="edit__btn"><i class="far fa-edit"></i></button>
<button class="delete__btn"><i class="far fa-trash-alt"></i></button>
</div>
`
item__sector.insertAdjacentHTML(position, item)
return item__sector
}
// load todo item from localstorage when page is loaded
document.addEventListener("DOMContentLoaded", getLocalStorage)
// add item to the item sector
add__btn.addEventListener("click", e => {
e.preventDefault()
const input__value = todo__input.value
if (input__value.trim() === "") { return }
createToDoItem(input__value)
saveLocalStorage(input__value)
todo__input.value = ""
})
// keypress Enter
document.addEventListener("keypress", e => {
if (e.keyCode == 13) {
e.preventDefault()
const input__value = todo__input.value
if (input__value.trim() === "") { return }
createToDoItem(input__value)
saveLocalStorage(input__value)
todo__input.value = ""
}
})
// the function on item (done, edit, and delete)
item__sector.addEventListener("click", e => {
const parent = e.target.parentElement
// done
if (e.target.classList.contains("done__btn")) {
e.target.nextElementSibling.classList.toggle("done__color")
}
// edit the todo item s
if (e.target.classList.contains("edit__btn")) {
if (e.target.previousElementSibling.disabled.disabled == true) {
e.target.previousElementSibling.disabled = !e.target.previousElementSibling.disabled
} else {
const todos = checkLocalStorage()
if (edit__index === -1) {
const valueBeforeEdit = e.target.previousElementSibling.getAttribute("value")
edit__index = todos.indexOf(valueBeforeEdit)
} else {
const valueAfterEdit = e.target.previousElementSibling.value
editLocalStorage(edit__index, valueAfterEdit)
edit__index = -1
}
e.target.previousElementSibling.disabled = !e.target.previousElementSibling.disabled
e.target.previousElementSibling.setAttribute("value", e.target.previousElementSibling.value)
}
}
// delete todo item
if (e.target.classList.contains("delete__btn")) {
parent.remove()
deleteLocalStorage(e.target.previousElementSibling.previousElementSibling)
}
})
// function for check todo status in the LocalStorage
function checkLocalStorage() {
let todos
if (localStorage.getItem("todos") === null) {
todos = []
} else {
todos = JSON.parse(localStorage.getItem("todos"))
}
return todos
}
// function for save localstorage
function saveLocalStorage(todo) {
const todos = checkLocalStorage()
todos.push(todo)
localStorage.setItem("todos", JSON.stringify(todos))
}
// function for get item and render to the screen from localstorage
function getLocalStorage() {
const todos = checkLocalStorage()
todos.forEach(todo => {
createToDoItem(todo)
})
}
// edit localStorage
function editLocalStorage(editIndex, editValue) {
const todos = checkLocalStorage()
todos.splice(editIndex, 1, editValue)
localStorage.setItem("todos", JSON.stringify(todos))
debugger
}
Note:
There is an edge case of having more than one todo item with the same value that you need to solve for.
I have a react component which renders a list item with individual OnClick.
In order to find out which item was clicked, the handler accepts a parameter. The handler does get invoked - but no matter which item is clicked - console always logs item3 (as if item3 is clicked). What am I doing wrong here?
class Item {
constructor(props) {
super(props);
this.onItemClickHandler = this.onItemClickHandler.bind(this)
}
onItemClickHandler (itemName) {
console.log("Clicked " + itemName)
}
render() {
this.items = ["item1", "item2", "item3"]
var lis = []
for (var liName in this.items) {
var liName2 = this.items[liName]
console.log("Adding " + this.items[liName])
lis.push(<li className="item-ListItem" key={this.items[liName]} onClick={() => this.onItemClickHandler(this.items[liName])}><span>{this.items[liName]}</span></li>)
}
return (
<div className="item">
<label className="item-Header"><u>items</u></label>
<ul className="item-List">
{lis}
</ul>
</div>
);
}
This line:
onClick={() => this.onItemClickHandler(this.items[liName])}>
appears to be correct.
The issue is that you are not capturing the value of this.items[liName] correctly because by the time you reach the third item iteration the onClick handler will always have the value of this.items[liName] set to the third item.
The solution for that is using closure to capture the value correctly, i edited your code and created a fully working example in this link
https://codesandbox.io/s/3xrp6k9yvp
Also the example code is written below with the solution
class App extends Component {
constructor(props) {
super(props);
this.onItemClickHandler = this.onItemClickHandler.bind(this);
}
onItemClickHandler(itemName) {
console.log("Clicked " + itemName);
}
render() {
this.items = ["item1", "item2", "item3"];
var lis = [];
for (var liName in this.items) {
var liName2 = this.items[liName];
console.log("Adding " + this.items[liName]);
//the clickHandler function here is the solution we created a function that get executed immediately each iteration and return a new function that has the correct value of `this.items[liName]` saved
var clickHandler = (item => {
return event => {
this.onItemClickHandler(item);
};
})(this.items[liName]);
lis.push(
<li
className="item-ListItem"
key={this.items[liName]}
onClick={clickHandler} // here we use the clickHandler function directly
>
<span>
{this.items[liName]}
</span>
</li>
);
}
return (
<div className="item">
<label className="item-Header">
<u>items</u>
</label>
<ul className="item-List">{lis}</ul>
</div>
);
}
}
For more info and examples about closures check this link
Edit We can use let in ES6 instead of var in our for loop as mentioned by #ArchNoob because using let will make the liName block scoped
Please take care of indentation while posting the code. Its very difficult to understand without that. You have to make use of closure. whenever the loop gets over liName variable gets set to last index as scope chain will keep the liName value to last one. The solution is not make a new scope between handler and click handler function where it is callled.
Here is the solution:
class Test extends React.Component {
constructor(props) {
super(props)
this.onItemClickHandler =
this.onItemClickHandler.bind(this)
}
onItemClickHandler(itemName) {
debugger
console.log("Clicked " + itemName)
}
render() {
this.items = ["item1", "item2", "item3"]
var lis = []
for (var liName in this.items) {
var liName2 = this.items[liName]
console.log("Adding " + this.items[liName])
debugger
lis.push( <li className = "item-ListItem"
key = {
this.items[liName]
}
onClick = {
((item) => {
return () => this.onItemClickHandler(item)
})(this.items[liName])
}
>
<span>
{this.items[liName]}
</span>
</li>
)
}
return (
<div>
<div className="item">
<label className="item-Header">
<u>items</u>
</label>
<ul className="item-List" >
{lis}
</ul>
</div>
</div>
)
}
}
ReactDOM.render(<Test />, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I will do it the recommended way.
First make a separate component of list-item.
To render list in react Js
//Handler
clickHandler=(id)=>{
console.log(`Clicked on Item with Id ${id}`);
}
//render method
render(){
let items = null;
if(this.state.isAnyItem){
<div className="items-list">
{
this.state.items.map((item)=>{
<Item key={item.id} item={item} click={(item.id)=>this.clickHandler(item.id)}/>
})
}
</div>
}
return (
<div>
{/*Some JSX here*/}
{items}
</div>
)
}
And Now the Item Component like
<div onClick={props.click}>
{/*Some JSX Here*/}
<h3>{item.name}</h3>
</div>