Why is splice method removing only first array index in React? - javascript

I'm making a small application in React with the PokeAPI and am having issues with using the splice() method to remove an element (pokemon) from the array (team). No matter which element I pick to remove it only removes the first element in the array.
This is the function -- which is being passed down through props -- I'm using in order to delete the item.
removePokemon = (index) => {
const team = [...this.state.team]
team.splice(index, 1)
this.setState({team})
}
And here is the Team component where it's actually being used.
import React, { Component } from 'react';
import Button from 'react-bootstrap/Button'
class Team extends Component {
render() {
return (
<div>
<h2>{this.props.trainer && <p>{this.props.trainer}'s Team</p>}</h2>
{this.props.team &&
<div>
{this.props.team.map((pokemon, i) => (
<div key={pokemon.id}>
<span className='cardHeader'>#{pokemon.id} - {pokemon.name}</span>
<img src={pokemon.sprites.front_default} alt={pokemon.name}/>
<Button onClick={this.props.removePokemon}>Remove from team</Button>
</div>
))}
</div>
}
</div>
);
}
}
export default Team;

You don't pass an argument index to your function removePokemon:
You need to edit one row:
<Button onClick={() => this.props.removePokemon(i)}>Remove from team</Button>

Since you have not passed index as argument to onClick={this.props.removePokemon}.
index inside removePokemon method refers the event object. So the code
team.splice(index, 1) evaluates to team.splice(eventObject, 1).
That's why splice is removing first element of the array.
You can change to onClick={() => this.props.removePokemon(i)}to remove the element you want.

Related

List Item is Still visible even after deleting from array as well as local storage

I have a button named yes in the child component where I delete a list item from array and local storage using the props.id passed from the parent component.
The problem is the item is deleted from array and local storage but is still visible on the screen until I press delete button on another item in the parent component.
when I press delete button in the parent component it opens an overlay. When I press yes button on overlay I want list item to be removed from the screen immediately.
here is the code in the child component.
import React, { useCallback, useState } from "react";
import styles from "./Delete.module.css";
function Delete(props) {
// console.log();
const store = props.store;
const [no, setNo] = useState(false);
let [deleted, setDelted] = useState(store);
console.log(deleted);
console.log("Length :" + store.length);
const noBtnHandler = () => {
console.log("clicked");
setNo(!no);
props.setDel(false);
};
const yesBtnHandler = () => {
console.log("Dlete.js :" + props.id);
const filteredStore = deleted.filter((task) => task.id !== props.id);
setDelted([...filteredStore]);
localStorage.removeItem(props.id);
// console.log(deleted);
setNo(!no);
};
return (
<div className={`${no === false ? styles.del : styles.delOn}`}>
<section>
<div>
<h3>Are you Sure ?</h3>
</div>
<div>
<button type="button" onClick={yesBtnHandler}>
{" "}
Yes{" "}
</button>
<button type="button" onClick={noBtnHandler}>
{" "}
No{" "}
</button>
</div>
</section>
</div>
);
}
export default Delete;
You are passing store from the parent component to the Delete Component and setting a new state here 'deleted'. so you are only calling the setDeleted on the Delete component which wont affect the parent component.
The correct implementation is to have the store state in the parent component if you don't already have it. It is will still be same like deleted state but possibly with a better name. Say const [store, setStore] = useState([])
Define a function to filter out a particular record just like you have in the yesBtnHandler handler. but this function will be defined in the parent component. Say as an example
const removeRecord = (id) => {
const filteredStore = store.filter((task) => task.id !== id);
localStorage.removeItem(id);
setStore(filteredStore);
}
You now need to pass the a function to the Delete Component from the parent rather than passing the whole store. Like
<Delete removeFunc= {() => { removeRecord(id) }} />
After passing this function, you need to call it in your yesBtnHandler function. Like
function Delete({removeFunc}) {
...
const yesBtnHandler = () => {
removeFunc();
setNo(!no);
};
}
Try remove the trailing ...
const yesBtnHandler = () => {
console.log("Dlete.js :" + props.id);
const filteredStore = deleted.filter((task) => task.id !== props.id);
setDelted([filteredStore]);
//or setDelted(filteredStore);
localStorage.removeItem(props.id);
// console.log(deleted);
setNo(!no);
};
my understanding of this is that you're trying to change the state of a parent component from a child component. If that's what you're intending to do then you can do the following:
Define the function Delete(id) {...} inside the parent component rather than the child component.
Next, you'll have to pass both the function and the array to your child component, something like this: <ChildComponent array={array} onDelete={Delete}, where array is the array in your parent component and Delete is the function to delete an item from the array.
Finally, in your child component, with the props passed in correctly, i.e, function ChildComponent({array, Delete}) {...}you can now have access to the array in your parent component, and actually modify it like you'd like. To fire the event listener on the yes button in the child component, do this: <button type="button" onClick={() => Delete(array.id)}> {" "} Yes{" "} </button>
I hope this will be helpful

Get getAttribute from button and toggle class name to body react hooks

i want to improve my code, with several buttons that has custom class names (attr), when clicked should add to body tag (toggle), now is adding the first button only because for ("button")[0] but should work for each button
import React, { useState, useEffect } from "react"
function Test() {
const [isClass, setIsClass] = useState(false)
useEffect(() => {
const x = document.getElementsByTagName("button")[0].getAttribute("custom-class")
document.body.classList.toggle(x, isClass)
}, [isClass])
return (
<>
<button custom-class='test1' onClick={() => setIsClass(!isClass)}>
Setting test1 className
</button>
<button custom-class='test2' onClick={() => setIsClass(!isClass)}>
Setting test2 className
</button>
</>
)
}
export default Test
Thanks
Please use this code.
let oldStyle = "";
const handleClick = (index) => {
const x = [...document.getElementsByTagName("button")].map(value => value.getAttribute("custom-class"));
document.body.classList.contains(x[index]) ? document.body.classList.remove(x[index]) : document.body.classList.add(x[index]);
if(document.body.classList.length > 1) document.body.classList.replace(oldStyle, x[index]);
oldStyle = x[index];
}
return (
<>
<button custom-class='test1' onClick={() => handleClick(0)}>
Setting test1 className
</button>
<button custom-class='test2' onClick={() => handleClick(1)}>
Setting test2 className
</button>
</>
)
It is better not to use DOM querying and manipulation directly with elements that are created and controlled by react. In your particular example it is ok to use document.body, but not ok to search for buttons, especially when you try to find them by tag name. To actually toggle a class in classList you don't need second parameter in most cases, so additional state is also not needed.
React way to get reference to element renderend by React would be to use Ref. However, in your particular case side effect can be launched inside event handler, so you don't need useEffect or useRef.
Your onClick handler can accept event object that is Synthetic Event. It holds property target that holds reference to your button.
So, the easiest way would be simply to write like this:
function Test() {
function clickHandler(event) {
let classToToggle = event.target.getAttribute("custom-class");
document.body.classList.toggle(classToToggle);
}
return (
<>
<button key="test1" custom-class="test1" onClick={clickHandler}>
Setting test1 className
</button>
<button key="test2" custom-class="test2" onClick={clickHandler}>
Setting test2 className
</button>
</>
);
}
export default Test;
If you need to have only single className from the list, you can decide which class to enable or disable with a bit of a state. Since anything can add classes on body it might be useful to operate only on some set of classes and not remove everything.
Also, not mentioned before, but consider using data attribute as its purpose is to keep some additional data.
function Test() {
// this can come from props or be hardcoded depending on your requirements
// If you intend to change it in runtime, consider adding side effect to cleanup previous classes on body
let [classesList] = React.useState(["test1", "test2"]);
let [activeClass, setActiveClass] = React.useState("");
// You can switch actual classes in effect, if you want to
function clickHandler(event) {
let classToToggle = event.target.dataset.customClass;
// we remove all classes from body that are in our list
document.body.classList.remove(...classesList);
if (activeClass === classToToggle) {
setActiveClass("");
} else {
// if class not active - set new one
document.body.classList.add(classToToggle);
setActiveClass(classToToggle);
}
}
return (
<>
{classesList.map((cn) => (
<button key="cn" data-custom-class={cn} onClick={clickHandler}>
Setting {cn} className
</button>
))}
</>
);
}

Can't remove specific elements from array by index number from dynamic div element in react?

I can't remove specific elements of array by index number from any dynamic div
const { useState } = React;
function Check(){
var [Children, setChildren] = useState([])
function RemArr(docs){
const temp = [...Children]
temp.splice(docs["i"], 1);
setChildren(temp)
}
function Product(docs) {
var document = (<React.Fragment>
<button onClick={()=>RemArr(docs)}>
List {docs["i"]}
</button>
<p></p>
<input/>
<p></p>
</React.Fragment>);
setChildren([...Children, document])
}
return (<React.Fragment>
<div>Check</div>
{Children}
<button
onClick={()=>{Product({fix:false, i:Children.length})}}
>Add List </button>
</React.Fragment>)
}
ReactDOM.render(<Check />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
When i want to remove any specific div then not only this div remove from array, also remove next div from array.
Eg.
array = [0,1,2,3,4,5,6,7,8,9]
If I want to remove only 6 from the array, however, it instead removes all the values from 6 to 9, and so I am left with just:
array = [0,1,2,3,4,5]
I think the problem originates from this function:
function RemArr(docs){
const temp = [...Children]
temp.splice(docs["i"], 1);
setChildren(temp)
}
Your RemArr function has a closure over Children, meaning that clicking on the remove button will call a potentially old declaration of remArr, where inside that remArr, the Children array will refer back to the Children array state from when you originally added the click event handler, rather than the current state of Children.
To overcome this, you can componentize Product rather than making it a function, and drive the Children based of some state in Choice, which you can use to generate your Product elements:
const {useState} = React;
function Product({docs, onClick}){
return <React.Fragment>
<button onClick={onClick}>
List {docs.i}
</button>
<p></p>
<input/>
<p></p>
</React.Fragment>;
}
function Check() {
const [products, setProducts] = useState([]);
function remArr(index){
const temp = [...products];
temp.splice(index, 1);
setProducts(temp);
}
function addProduct() {
const prevNum = products[products.length-1];
setProducts([...products, {
fix: false,
i: prevNum === undefined ? 0 : prevNum.i + 1
}]);
}
return (<React.Fragment>
<div>Check</div>
{products.map(
(doc, i) => <Product key={doc.i} docs={doc} onClick={() => remArr(i)}/>
)}
<button
onClick={addProduct}
>Add List</button>
</React.Fragment>);
}
ReactDOM.render(<Check />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
The above .map() method that creates new <Product /> components "updates"/re-sets the onClick function of all the product components to point to the "latest" declared remArr function, which has access to the correct/up to date products array. This is unlike your example, where the old JSX objects inside of the Children array still have onClick attributes that refer to the old declarations of the remArr function (which have access to the old Children array state)

React: Button click not writing to console

I have a button and for testing purposes, I want to write to the console the index of an array element. More specifically, I have a button in button.js, and that button is displayed on each array element in the IncomeOutputList array. When clicked on, I want each button to print to the console the index of the corresponding IncomeOutputList array element.
For example, by clicking on the button of the second element shown in the image below, I want the console to display index 1 (the first element is the topmost rectangle, which is a blank array element).
Here is a picture of an array element with the button, the button appears while hovering above the number for each array element:
Currently when the page renders, all of the indices of the array are displayed in console, not sure why.
I hope I made my question clear!
button.js:
import React from 'react';
const Button = ({buttonType, handler}) => (
<>
<div className="item__delete">
<button className={buttonType} onClick={handler}>
<i className="ion-ios-close-outline"></i>
</button>
</div>
</>
)
export default Button;
ValueOutput.js:
import React from 'react';
import Button from '../buttons/Button';
//move item__value element to left when hovering over it, and make delete button appear
const ValueOutput = ({type, value, handleClick}) => {
return (
<>
<div className="right clearfix">
<div className="item__value">{type} {value}</div>
<Button buttonType="item__delete--btn" handler={handleClick}/>
</div>
</>
)
}
export default ValueOutput;
IncomeOutput.js:
import React from 'react';
import ValueOutput from './ValueOutput';
const IncomeOutput = ({ desc, type,id, value, handleButton }) => {
//id = inc-{id}
return (
<>
<div className="item clearfix income" id={id}>
<div className="item__description">{desc}</div>
<ValueOutput
type={type}
value={value}
handleClick={handleButton}
/>
</div>
</>
)
}
export default IncomeOutput;
IncomeOutputList.js:
import React from 'react';
import IncomeOutput from './IncomeOutput';
// list will be list of income objects
const IncomeOutputList = ({ list }) => {
const handler = (i) => {
console.log(i);
console.log('the test');
}
return (
<div className="income__list">
<div className="income__list--title">INCOME</div>
{list.map((item, index) => <IncomeOutput
id={item.id}
value={item.incomeValue}
type={item.budgetType}
desc={item.desc}
handleButton={handler(index)}
/>
)}
</div>
)
}
You are passing handler(index) as your event handler. Since that doesn't return anything you are effectively passing undefined as your handler. You will want to change your handler method to return a function:
const handler = (i) => {
return () => {
console.log(i);
console.log('the test');
};
};
You could also just wrap your call to handler in a function, buttonHandle={() => handler(index)} - This is effectively the same thing.
The problem is that the handler function is executed right away when the code is encountered.
Whenever you have () the function will execute right away when encountered. It is not waiting for the event to fire.
Here is what you can do:
handleButton={() => handler(index)}

How to dynamically create and delete elements(containing inputs, p-s, buttons), which has their own remove-button

Well, the case is:It is a react application, there is a button, which creates an element with its own remove button. The created element contains inputs, paragraph tags and buttons. The bug i cant remove for several days already is: let's assume deleting the n-th such element, which has a paragraph p {index} /p (index=n) and the input input type="text" {index} /input, the thing is that after removing the element(we cant see it's paragraph anymore) the input's text is replacing the n+1-th input's text, the n+1-th input's text replacing the n+2-th input's text and so on. When n+k is equal to the list size, n+k-th input disappears.
const defaultList=[];
const List = (props) => (
<ul>
{props.items.map((item, index) => (
<li key={index}>
{item}
<br />
<button onClick={() =>
props.removeItem(index)}>Remove</button>
</li>
))}
</ul>
);
export default class Accounts extends React.Component {
constructor(props) {
super(props);
this.addItem = this.addItem.bind(this);
this.state={
items: defaultList,
created:0
}
}
removeItem(removeIndex) {
this.setState((state) => ({
...state,
items: this.state.items.filter((item, index) => index !== removeIndex)
}))
}
addItem(){
const temp = this.state.items;
temp.push(
<div>
<p>{this.state.created}</p>
<input name="text" type="text" id={"input "+this.state.created}/>
</div>
);
this.setState(() => ({
items: temp,
created:++this.state.created
}))
}
render(){
return(<div>
<List items={this.state.items} removeItem={this.removeItem.bind(this)} />
<button onClick={this.addItem}>Add item</button>
</div>
}
}
Well, I cant provide images how it works, cuz this is my first post and I need at least 10 rating to add images :/
The only problem in this code i see you should write
const temp = [...this.state.items];
in addItem handler, otherwise everything is perfectly ok
The problem is in using Array.prototype.push() method, which is changing the original array and returns as inline result the new array length. When working with arrays in react state better use methods like map, filter and reduce, because they don’t affect target array and in contrast to push, map returns a new, modified array. Hope it helps and in case you want source of this idea

Categories