Modifying Hook setState for an Array in React using Splice - javascript

I'm trying to splice an array hook State, so I can remove items from my list. I know you're not supposed to modify any hook states directly, so I made a copy into array2 before settting it, but it's removing all items below the one I click, instead of just the one I click. Any ideas?
export default function Shopping() {
const [item, setItem] = useState('Default');
const [list, setList] = useState([]);
const [array, setArray] = useState([]);
let array2 = [];
const name = ({ target }) => setItem(target.value);
const remove = ({ target }) => {
for (let x = 0; x <= array.length; x++) {
if (array[x] === target.value) {
array2 = list;
array2 = array2.splice(x, 1);
}
setList([...list, array2]);
}
};
const create = () => {
if (item !== 'Default' && document.getElementById('box').value !== '') {
let value = document.getElementById('box').value;
setArray([...array, value]);
setList([
...list,
<div>
<p>
{' '}
{item} Qty: 1<button>+</button>
<button>-</button>
<button
value={document.getElementById('box').value}
onClick={remove}
>
x
</button>
</p>
</div>,
]);
} else {
alert('Please enter an Item');
}
document.getElementById('box').value = '';
setItem('Default');
};
const clear = () => setList([]);
return (
<div>
<input type='textbox' id='box' onChange={name} />
<input type='button' value='Add' onClick={create} />
<input type='button' value='Clear' onClick={clear} />
{list}
</div>
);
}

You should just store the strings in the list array and display the elements using a map function:
export default function Shopping() {
const [item, setItem] = useState('Default');
const [list, setList] = useState([]);
const name = ({ target }) => setItem(target.value);
const remove = (index) => {
const filteredList = [...list].splice(index, 1);
setList(filteredList);
};
const create = () => {
if (item !== 'Default' && item !== '') {
setList([...list, item]);
} else {
alert('Please enter an Item');
}
setItem('Default');
};
const clear = () => setList([]);
return (
<div>
<input type='textbox' id='box' onChange={name} />
<input type='button' value='Add' onClick={() => create()} />
<input type='button' value='Clear' onClick={() => clear()} />
{list.map((it, index) => {
return (
<div key={index}>
<p>
{' '}
{it} Qty: 1<button>+</button>
<button>-</button>
<button value={it} onClick={() => remove(index)}>
x
</button>
</p>
</div>
);
})}
</div>
);
}
```

Just Update the code in remove function because array2 = list also copies the reference of list array so when you update the array2 it also update the list array.so replace the remove function code with given code below .
if ((array[x])===target.value){
array2 = list.map(item=>item)
array2 = array2.splice(x,1)
}

Related

How to set array of values to state and fetch during submit in React

Depending on the count, the number of text box will be generated.
When I try to set the text box value to the state, textbox is not accepting more than one value.
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const [option, setOption] = useState([]);
const Generator = (count) => {
let textArr = [];
for (let i = 0; i < count.count; i++) {
let name = "txt" + i;
textArr.push(<input type="text" key= {name} id={name} onChange={e => onChangeOptions(e,i)} value={getOptionValue(i)}/>);
}
return textArr;
};
const getOptionValue = (i) => {
let opt = [...option];
return opt[i];
}
const onChangeOptions = (e,i) => {
let val = e.target.value;
let opt = [...option];
opt[i] = val;
setOption(opt);
console.log(opt);
}
const onSubmitForm = (e) => {
console.log(option);
}
return (
<div>
<form onSubmit={onSubmitForm}>
<input
type="text"
id="count"
onChange={(e) => setCount(e.target.value)}
/>
<br />
<Generator count={count} />
<button>Submit</button>
</form>
</div>
);
}
Generator is defined inside your component, so it's a different function on each render. React thinks a different element is being rendered each time, which makes the input lose focus. Instead, directly create the array of inputs in the JSX with Array#map:
{
[...Array(+count)].map((_, i) => (
<input type="text" key={"txt" + i} id={"txt" + i} onChange={e => onChangeOptions(e,i)} value={getOptionValue(i)}/>
))
}
Alternatively, extract Generator into a separate component. Do not define it inside another component.

How can I how to click specific button but not all buttons

I am trying to create a button Add and Remove but i'm having issues that when I click one button and all the others was clicked as well.
Here is my following code:
function MyFruits() {
const fruitsArray = [
'banana',
'banana',
'orange',
'orange',
'strawberry',
'blackberry',
];
const cartArray : any[] = [];
let unique = [...new Set(fruitsArray)];
unique.sort((a, b) => {
return a.localeCompare(b);
})
const [addButton, setAddButton] = useState(true);
const [removeButton, setRemoveButton] = useState(false);
const [itemList, setItemList] = useState(fruitsArray)
const [cart, setCart] = useState<typeof cartArray>([])
const addClick = (fruit: any, index: any) => {
setItemList([...itemList]);
setCart([...cart, fruit]);
setAddButton(false);
setRemoveButton(true);
}
const removeClick = (fruit: any, index: any) => {
let removeItem = [...cart];
removeItem = removeItem.filter((cartItem) => cartItem !== fruit);
setCart(removeItem);
setAddButton(true);
setRemoveButton(false);
}
return (
<div className="App">
<header className="App-header">
<ul>
{unique.map((fruit, index) => (
<div>
<li key={index}>{fruit}</li>
{itemList.length !== 1 && addButton && (
<button onClick={() => addClick(fruit, index)}>Add</button>
)}
{removeButton && (
<button onClick={() => removeClick(fruit, index)}>Remove</button>
)}
</div>
))}
</ul>
<div className="cart">
Cart: {cart.map(cartItem => (
<p>{cartItem}</p>
))}
</div>
</header>
</div>
);
}
export default MyFruits;
How can I improve my click function for Add and Remove ?
You've to track the addButton and removeButton state changes for each and every fruit you've in your array. So better you create state objects such as addButtonFruits and removeButtonFruits to track them.
I've created a JavaScript implementation similar to your code and try to use it as a supportive material for your implementation.
import React, { useEffect, useState } from "react";
function App() {
const fruitsArray = [
"banana",
"banana",
"orange",
"orange",
"strawberry",
"blackberry",
];
const cartArray = [];
let unique = [...new Set(fruitsArray)];
unique.sort((a, b) => {
return a.localeCompare(b);
});
const [addButtonFruits, setAddButtonFruits] = useState({});
const [removeButtonFruits, setRemoveButtonFruits] = useState({});
const [itemList, setItemList] = useState(fruitsArray);
const [cart, setCart] = useState([]);
/*
instead of having this useEffect, you can directly set the default state using useState as follows:
const [addButtonFruits, setAddButtonFruits] = useState(
{
banana: true,
orange: true,
strawberry: true,
});
*/
useEffect(() => {
const initialAddButtonFruits = {};
fruitsArray.forEach((each) => (initialAddButtonFruits[each] = true));
console.log("initial add-fruit-buttons state: ", initialAddButtonFruits);
setAddButtonFruits(initialAddButtonFruits);
}, []); // only executed for initial rendering
const addClick = (fruit, index) => {
setItemList([...itemList]);
setCart([...cart, fruit]);
const addButtonFruitsTemp = addButtonFruits;
addButtonFruitsTemp[fruit] = false;
setAddButtonFruits(addButtonFruitsTemp);
const removeButtonFruitsTemp = removeButtonFruits;
removeButtonFruitsTemp[fruit] = true;
setRemoveButtonFruits(removeButtonFruitsTemp);
};
const removeClick = (fruit, index) => {
let removeItem = [...cart];
removeItem = removeItem.filter((cartItem) => cartItem !== fruit);
setCart(removeItem);
const addButtonFruitsTemp = addButtonFruits;
addButtonFruitsTemp[fruit] = true;
setAddButtonFruits(addButtonFruitsTemp);
const removeButtonFruitsTemp = removeButtonFruits;
removeButtonFruitsTemp[fruit] = false;
setRemoveButtonFruits(removeButtonFruitsTemp);
};
console.log("addButtonFruits: ", addButtonFruits);
console.log("removeButtonFruits: ", removeButtonFruits);
return (
<div className="App">
<header className="App-header">
<ul>
{unique.map((fruit, index) => (
<div>
<li key={index}>{fruit}</li>
{itemList.length !== 1 && addButtonFruits[fruit] && (
<button onClick={() => addClick(fruit, index)}>Add</button>
)}
{removeButtonFruits[fruit] && (
<button onClick={() => removeClick(fruit, index)}>
Remove
</button>
)}
</div>
))}
</ul>
<div className="cart">
Cart:{" "}
{cart.map((cartItem) => (
<p>{cartItem}</p>
))}
</div>
</header>
</div>
);
}
export default App;
Following is the view
Hope this will solve your issue.

Delete item at selected index when clicking delete button - React

I'm currently having an issue where when I click on the delete button for a particular element it only deletes the first item in list array every time rather than the selected one.
Any help with this would be greatly appreciated! :)
import "./css/App.css";
import React, { useRef } from "react";
function App() {
const [item, setItem] = React.useState("");
const [list, updateList] = React.useState([]);
const toDoItem = (item) => {
let newItem = { text: item.target.value, key: Date.now() };
setItem(newItem);
};
const addItem = () => {
updateList((prevState) => [...list, item]);
document.getElementById("Input").value = "";
};
const deleteItem = (index) => {
updateList((prevState) => {
let items = [...prevState];
console.log(items);
items.splice(index, 1);
return items;
});
};
return (
<div className="App">
<h2>Type a todo item you want to complete</h2>
<input
type="text"
placeholder="Add a todo..."
id="Input"
onChange={toDoItem}
/>
<button id="Add" onClick={addItem}>
Add
</button>
{list.map((item, index) => {
return (
<ol key={index}>
{item.text[0].toUpperCase() + item.text.slice(1).toLowerCase()}
<button>Edit</button>
<button id="Delete" onClick={deleteItem}>
✗
</button>
{/* <button onClick={test}>test</button> */}
</ol>
);
})}
</div>
);
}
export default App;
You forgot pass index when call deleteItem
onClick={() => deleteItem(index)}

Simulate "shift" pressing key on checkbox to select multiple rows

I have the following input
<input type="checkbox" checked={isChecked}
onChange={handleOnChange}/>
and my function is this
const handleOnChange = () => {
let element:any = document.querySelector('input');
element.onkeydown = (e: { key: any; }) => alert(e.key);
element.dispatchEvent(new KeyboardEvent('keydown',{'key':'Shift'}));
setIsChecked(!isChecked);
};
This checkbox is created dinamically as I add new rows and I would like to simulate holding the key "shift" so that when I check multiple checkboxes these rows remain selected.
I am using reactjs.
There's no native way to do it but you can implement it based on the index you have checked.
const checkboxes = new Array(20).fill(null);
export default function App() {
const [checked, setChecked] = useState([]);
const lastChecked = useRef(null);
const handleChange = useCallback((e) => {
const index = Number(e.target.dataset.index);
if (lastChecked.current !== null && e.nativeEvent.shiftKey) {
setChecked((prev) => {
const start = Math.min(lastChecked.current, index);
const end = Math.max(lastChecked.current, index);
return uniq([...prev, ...range(start, end), end]);
});
return;
}
if (e.target.checked) {
lastChecked.current = index;
setChecked((prev) => [...prev, index]);
} else {
lastChecked.current = null;
setChecked((prev) => prev.filter((i) => i !== index));
}
}, []);
return (
<div>
{checkboxes.map((_, i) => (
<div key={i}>
<label>
<input
checked={checked.includes(i)}
data-index={i}
type="checkbox"
onChange={handleChange}
/>
checkbox {i}
</label>
</div>
))}
</div>
);
}

How to add a whole array to useState variable

I'm trying to add a whole array to useState variable
import React, { Fragment, useState, useEffect } from 'react';
import { Form, Button, Popover, OverlayTrigger } from 'react-bootstrap';
const Filter = props => {
const [formData, setFormData] = useState({
filter: ''
});
const [items, setItems] = useState([]);
const [retrievedItems, setRetrievedItems] = useState([]);
const addToFilter = newFilter => {
let retrievedFilter = ["da vinci","paris", "london"];
console.log(retrievedFilter);
if (retrievedFilter.length > 0) {
setRetrievedItems([...retrievedItems, retrievedFilter]);
retrievedFilter = 0;
setRetrievedItems([...retrievedItems, newFilter]);
} else {
setItems([...items, newFilter]);
}
console.log('items are: ', items);
console.log('retrieve filter', props.retrievedFilter);
console.log('retrieved items: ', retrievedItems);
};
useEffect(() => {
console.log('useEffect ', retrievedItems);
}, [retrievedItems]);
const deleteFilter = index => {
// props.retrievedFilter.splice(index, 1);
items.splice(index, 1);
setItems([...items]);
// setItems([...props.retrievedFilter, ...items]);
console.log(items);
};
const { filter } = formData;
const onChange = e => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const onSubmit = e => {
e.preventDefault();
addToFilter(filter);
// Passing filter data up (i.e: to components that use <Filter />)
props.filterData(filter);
//Close the Popover
document.body.click();
};
const popover = (
<Popover id="popover-basic">
<Form>
<Form.Group controlId="formGroupEmail">
<Form.Label>Add New Filter</Form.Label>
<Form.Control
type="text"
placeholder="New Filter"
name="filter"
onChange={e => onChange(e)}
/>
</Form.Group>
<Button variant="dark" type="submit" onClick={e => onSubmit(e)}>
Add
</Button>
</Form>
</Popover>
);
return (
<Fragment>
<label>
<p className="filter-title">{props.title}</p>
</label>
<div className={props.className ? props.className : 'filter'}>
{!props.retrievedFilter
? items.map((item, index) => {
return (
<div className="filter-text" key={index}>
{item}
<Button
className="filter-button"
size="sm"
onClick={() => deleteFilter(index)}
>
X
</Button>
</div>
);
})
: props.retrievedFilter.map((item, index) => {
return (
<div className="filter-text" key={index}>
{item}
<Button
className="filter-button"
size="sm"
onClick={() => deleteFilter(index)}
>
X
</Button>
</div>
);
})}
<OverlayTrigger
trigger="click"
placement="right"
rootClose
overlay={popover}
>
<p className="text-field">Type new one</p>
</OverlayTrigger>
</div>
</Fragment>
);
};
export default Filter;
however retrievedItems shows as an empty array in the console.
any help would be appreciated.
setState is async. You have to console.log inside an effect hook with the array as a parameter.
useEffect(() => console.log(retrieved_items), [ retrievedItems ])
The second parameter ensures that the effect fires in repose to a change in the values passed to it.
Per my comment, here is a code snippet that I think does what you want.
I couldn't get it running in SO but here's a codepen: https://codepen.io/anon/pen/PrYYmz?editors=1010 (watch the chrome console as you add items)
import React, {
Fragment,
useState,
useEffect
} from 'react';
const Filter = props => {
const [formData, setFormData] = useState({filter: ''});
const [items, setItems] = useState([]);
const [retrievedItems, setRetrievedItems] = useState([]);
const addToFilter = newFilter => {
let retrievedFilter = ["da vinci", "paris", "london"];
console.log('add', retrievedFilter);
if (retrievedFilter.length > 0) {
setRetrievedItems([...retrievedItems, retrievedFilter]);
retrievedFilter = 0;
setRetrievedItems([...retrievedItems, newFilter]);
} else {
setItems([...items, newFilter]);
}
console.log('items are: ', items);
console.log('retrieve filter', props.retrievedFilter);
console.log('retrieved items: ', retrievedItems);
};
useEffect(() => {
console.log('useEffect ', retrievedItems);
}, [retrievedItems]);
const deleteFilter = index => {
// props.retrievedFilter.splice(index, 1);
items.splice(index, 1);
setItems([...items]);
// setItems([...props.retrievedFilter, ...items]);
console.log(items);
};
const {filter} = formData;
const onChange = e => {
setFormData({ ...formData,
[e.target.name]: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
addToFilter(filter);
// Passing filter data up (i.e: to components that use <Filter />)
//props.filterData(filter);
//Close the Popover
document.body.click();
};
return (
<Fragment >
<label >
<p className = "filter-title" > {
props.title
} </p> </label> <
div className = {
props.className ? props.className : 'filter'
} > {!props.retrievedFilter ?
items.map((item, index) => {
return ( <
div className = "filter-text"
key = {index} > {item} <button className = "filter-button" size = "sm" onClick = {() => deleteFilter(index)}>X</button></div>
);
}) :
props.retrievedFilter.map((item, index) => {
return ( <div className = "filter-text" key = {index} > {item} <button className = "filter-button" size = "sm" onClick = {() => deleteFilter(index)} >X</button></div>);})} <input type = "text" placeholder = "New Filter" name = "filter" onChange = {e => onChange(e) }/>
<button variant = "dark" type = "submit" onClick = {e => onSubmit(e)} >Add</button>
</div>
</Fragment>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

Categories