moving list elements using react useState not working - reactjs

I have the following code:
import React, { useState } from "react";
export default function App() {
const [arr, setArr] = useState([
{ id: 1, name: "orange" },
{ id: 2, name: "lemon" },
{ id: 3, name: "strawberry" },
{ id: 4, name: "apple" }
]);
const onMoveUp = function (key) {
if (key === 0) return;
const items = arr;
const index = key - 1;
const itemAbove = items[index];
items[key - 1] = items[key];
items[key] = itemAbove;
console.log(items);
setArr(items);
};
const onMoveDown = function (key) {
const items = arr;
if (key === items.length - 1) return;
const index = key + 1;
const itemBelow = items[index];
items[key + 1] = items[key];
items[key] = itemBelow;
setArr(items);
};
return (
<div>
<ul>
{arr.map((item, key) => (
<li key={key}>
<div>
{item.id} - {item.name}
</div>
<div>
<span onClick={() => onMoveUp(key)}>▲</span>
<span onClick={() => onMoveDown(key)}>▼</span>
</div>
</li>
))}
</ul>
</div>
);
}
What it is supposed to do is move list elements up and down using the arrows. I have tried many ways but nothing seems to work. I am currently exploring useEffect. The array does change but it is not reflected in the UI. Please help.

It is not advisable to change the state value directly. It is better to make a copy of your state
Good learning
Here is an example with spread operator :
import React, { useState } from "react";
export default function App() {
const [arr, setArr] = useState([
{ id: 1, name: "orange" },
{ id: 2, name: "lemon" },
{ id: 3, name: "strawberry" },
{ id: 4, name: "apple" }
]);
const onMoveUp = function (key) {
if (key === 0) return;
const items = [...arr];
const index = key - 1;
const itemAbove = items[index];
items[key - 1] = items[key];
items[key] = itemAbove;
console.log(items);
setArr(items);
};
const onMoveDown = function (key) {
const items = [...arr];
if (key === items.length - 1) return;
const index = key + 1;
const itemBelow = items[index];
items[key + 1] = items[key];
items[key] = itemBelow;
setArr(items);
};
return (
<div>
<ul>
{arr.map((item, key) => (
<li key={key}>
<div>
{item.id} - {item.name}
</div>
<div>
<span onClick={() => onMoveUp(key)}>▲</span>
<span onClick={() => onMoveDown(key)}>▼</span>
</div>
</li>
))}
</ul>
</div>
);
}

In React, the UI must be changed to a new object in the state in order to be updated. A framework like React uses immutability for state management.
For example, in your function onMoveUp, items are the same array as arr made with useState. React does not update the UI because you mutate the same object and store this value in the state.
Save the new object in the state by modifying the code as follows:
const onMoveUp = function (key) {
if (key === 0) return;
const items = [...arr]; // Create a new object with Copy Object
const index = key - 1;
const itemAbove = items[index];
items[key - 1] = items[key];
items[key] = itemAbove;
console.log(items);
setArr(items);
};

Related

React can't get component to update when state changes

I am trying to update a videoId using state, but when the state changes, it does not update my component for some reason.
I've tried to spread the new data, but that isn't working either.
Here is my code:
const exercises = [
{
id: 1,
videoSrc:video,
},
{
id: 2,
videoSrc: video
},
{
id: 3,
videoSrc: video
},
{
id: 4,
videoSrc: video
},
];
export function WarmUpContent() {
const [videoId, setVideoId] = useState(1);
const [videoSrc, setVideoSrc] = useState(exercises[0].videoSrc);
function handleNext() {
setVideoId(videoId + 1);
console.log(videoId);
const result = exercises.find(({ id }) => id === videoId);
const video = result.videoSrc;
setVideoSrc([...video]);
}
return (
<>
<div className="">
<div className="mb-4">
<VideoPlayer src={videoSrc} />
</div>
<div className="">
<Button onClick={() => handleNext()} size="slim">
Next
</Button>
</div>
</div>
</>
);
}
The state is updated asynchronously, so the new state would only be available on the next render. A simple solution is to calculate the new id, and then set the state, and use to get the video src:
function handleNext() {
const newId = videoId + 1;
setVideoId(newId);
const result = exercises.find(({ id }) => id === newId);
const video = result.videoSrc;
setVideoSrc(video); // don
}
However, a better solution would be use only a single state (videoId). Update the videoId and then calculate the derived data (videoSrc) during render:
export function WarmUpContent() {
const [videoId, setVideoId] = useState(1);
function handleNext() {
setVideoId(id => id + 1); // use the update function when the value is dependent on prev value
}
const videoSrc = useMemo(() => {
const result = exercises.find(({ id }) => id === videoId);
return result.videoSrc;
}, [videoId]);
return (...);
}

How can I prevent re-rendering of functional child element

While creating a tic-tac-toe game on React js. whenever I click on a single tile, it re-renders for all other tiles too.
const Game = () => {
const [xIsNext, setXIsNext] = useState(true);
const [stepNumber, setStepNumber] = useState(0);
const [history, setHistory] = useState([{ squares: Array(9).fill(null) }]);
const updatedHistory = history;
const current = updatedHistory[stepNumber];
const winner = CalculateWinner(current.squares);
const move = updatedHistory.map((step, move) => {
const desc = move ? `Go to # ${move}` : "Game Start";
const jumpTo = (step) => {
setStepNumber(step);
setXIsNext(step % 2 === 0);
};
return (
<div key={move}>
<button className="btninfo" onClick={() => jumpTo(move)}>{desc}</button>
</div>
);
});
let status;
if (winner) {
status = `Winner is ${winner}`;
} else {
status = `Turn for Player ${xIsNext ? "X" : "O"}`;
}
const handleClick = (i) => {
const latestHistory = history.slice(0, stepNumber + 1);
const current = latestHistory[latestHistory.length - 1];
const squares = current.squares.slice();
const winner = CalculateWinner(squares);
if (winner || squares[i]) {
return;
}
squares[i] = xIsNext ? "X" : "O";
setHistory(history.concat({ squares: squares }));
setXIsNext(!xIsNext);
setStepNumber(history.length);
};
const handleRestart = () => {
setXIsNext(true);
setStepNumber(0);
setHistory([{ squares: Array(9).fill(null) }]);
};
return (
<div className="game">
<div className="game-board">
<div className="game-status">{status}</div>
<Board onClick={handleClick} square={current.squares} />
</div>
<div className="game-info">
<button className="btninfo" onClick={handleRestart}>Restart</button>
<div>{move}</div>
</div>
</div>
);
};
export default Game;
const CalculateWinner = (squares) => {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[b] === squares[c]) {
return squares[a];
}
}
return null;
};
and the Board component on which Parameter is passed is
const Board = (props) => {
const renderSquare = (i) => {
return <Square value={props.square[i]} onClick={() => props.onClick(i)} />;
};
return (
<div>
<div className="border-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="border-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="border-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
);
};
export default Board;
which leads to the single Square component mentioned below, which re-renders for all tiles if we click a single tile.
const Square = (props) => {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
};
export default Square;
I tried with useCallback on handleClick function, and kept the second parameter as an empty array, then also it didn't work.
How can I prevent the re-rendering of other tiles?
If a React component's parent rerenders, it will cause the child to rerender unless the component is optimized using React.memo or the shouldComponentUpdate life cycle method handles this.
Since yours is a functional component just do this while exporting:
export default React.memo(Square);
As mentioned in the docs, React.memo does a shallow check of props and if found different returns true. This can be controlled using the second argument of the function, which is a custom equality checker.
One of the props for Square is an object (the function - onClick={() => props.onClick(i)}). This is obviously created new everytime. A function object is equal only to itself.
You will have to use useCallback so the function is not created in every cycle.
const handleClick = useCallback((i) => {
const latestHistory = history.slice(0, stepNumber + 1);
const current = latestHistory[latestHistory.length - 1];
const squares = current.squares.slice();
const winner = CalculateWinner(squares);
if (winner || squares[i]) {
return;
},[history,latestHistory,squares]);
````
You might have to do the same here too:
````
const renderSquare = (i) => {
cont clickHandler = useCallback(() => props.onClick(i),[props.onClick]);
return <Square value={props.square[i]} onClick={clickHandler} />;
};
````

How to prevent re-rendering with callbacks as props in ReactJS?

I'm practicing with the new hooks functionnality in ReactJS by refactoring the code from this tutorial with TypeScript.
I am passing a callback from a parent component to a child component threw props that has to be executed on a click button event.
My problem is this: I have an alert dialog that appears twice instead of once when the game has been won.
I assumed this was caused by a component re-rendering so I used the useCallback hook to memoize the handleClickOnSquare callback. The thing is, the alert dialog still appears twice.
I guess I'm missing something that has a relation with re-rendering, does someone have an idea what might cause this behavior ?
Here is my code:
Game.tsx
import React, { useState, useCallback } from 'react';
import './Game.css';
interface SquareProps {
onClickOnSquare: HandleClickOnSquare
value: string;
index: number;
}
const Square: React.FC<SquareProps> = (props) => {
return (
<button
className="square"
onClick={() => props.onClickOnSquare(props.index)}
>
{props.value}
</button>
);
};
interface BoardProps {
squares: Array<string>;
onClickOnSquare: HandleClickOnSquare
}
const Board: React.FC<BoardProps> = (props) => {
function renderSquare(i: number) {
return (
<Square
value={props.squares[i]}
onClickOnSquare={props.onClickOnSquare}
index={i}
/>);
}
return (
<div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
);
};
export const Game: React.FC = () => {
const [history, setHistory] = useState<GameHistory>(
[
{
squares: Array(9).fill(null),
}
]
);
const [stepNumber, setStepNumber] = useState(0);
const [xIsNext, setXIsNext] = useState(true);
const handleClickOnSquare = useCallback((index: number) => {
const tmpHistory = history.slice(0, stepNumber + 1);
const current = tmpHistory[tmpHistory.length - 1];
const squares = current.squares.slice();
// Ignore click if has won or square is already filled
if (calculateWinner(squares) || squares[index]) return;
squares[index] = xIsNext ? 'X' : 'O';
setHistory(tmpHistory.concat(
[{
squares: squares,
}]
));
setStepNumber(tmpHistory.length);
setXIsNext(!xIsNext);
}, [history, stepNumber, xIsNext]);
const jumpTo = useCallback((step: number) => {
setHistory(
history.slice(0, step + 1)
);
setStepNumber(step);
setXIsNext((step % 2) === 0);
}, [history]);
const current = history[stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ?
'Go back to move n°' + move :
'Go back to the start of the party';
return (
<li key={move}>
<button onClick={() => jumpTo(move)}>{desc}</button>
</li>
);
});
let status: string;
if (winner) {
status = winner + ' won !';
alert(status);
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClickOnSquare={handleClickOnSquare}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
function calculateWinner(squares: Array<string>): string | null {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
types.d.ts
type GameHistory = Array<{
squares: Array<string>
}>;
type HandleClickOnSquare = (index: number) => void;
Thanks
Your code is too long to find the cause of extra rerender. Note that React might need an extra render.
To avoid an extra alert use useEffect:
let status: string;
if (winner) {
status = winner + ' won !';
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
useEffect(() => {
if (winner) alert(status)
}, [winner]);

Displaying a list of filtered products by the user input gatsbyJS + Firebase

I'm building a product catalog with gatsby and firebase, and I want to display a list of products
according to the product category that the user chooses. I'd fetch all products add pagination and now I need a filter menu or select, so I tried this to implement a filter..before that everything was going fine...
imports
...
// query
export const data = graphql`
query data($skip:Int!, $limit:Int!) {
products: allProducts(
skip: $skip,
limit: $limit,
sort: {fields: id, order: ASC}) {
edges {
node {
id
slug
categories
model
previewImg
description
category
}
}
}
}
`
//component
class ProductsSectionTemplate extends Component {
constructor(props){
super(props)
this.state={
products:[],
product:''
}
}
handleChange = event => {
this.setState({ value: event.target.value });
};
handleChangeProduct = event => {
this.setState({ product: event.target.value });
};
getUnique(arr, comp) {
const unique = arr
//store the comparison values in array
.map(e => e[comp])
// store the keys of the unique objects
.map((e, i, final) => final.indexOf(e) === i && i)
// eliminate the dead keys & store unique objects
.filter(e => arr[e])
.map(e => arr[e]);
return unique;
}
componentDidMount(){
const products = data.products.edges.map( product => ({
...product.node,
previewImg: product.node.previewImg,
category:product.node.category
}))
this.setState({
products: products
})
}
render(){
const { pageContext} = this.props
const products = this.state.products
const product = this.state.product
const { currentPage, numPages } = pageContext
const isFirst = currentPage === 1
const isLast = currentPage === numPages
const prevPage = currentPage - 1 === 1 ? "/" : (currentPage - 1).toString()
const nextPage = (currentPage + 1).toString()
const uniqueProduct = this.getUnique(this.state.products, "category");
const filterDropdown = products.filter( result => result.category === product );
return(
<Layout>
<h1>Productos</h1>
<select
value={this.state.product}
onChange={this.handleChangeProduct}
>
{uniqueProduct.map(product => (
<option key={product.id} value={product.category}>{product.category}</option>
))}
</select>
<ProductsSection products={filterDropdown}/>
{!isFirst && (
<Link to={`/products/${prevPage}`} rel="prev">
← Previous Page
</Link>
)}
{Array.from({ length: numPages }, (_, i) => (
<Link key={`pagination-number${i + 1}`} to={`/products/${i === 0 ? "" : i + 1}`}>
{i + 1}
</Link>
))}
{!isLast && (
<Link to={`/products/${nextPage}`} rel="next">
Next Page →
</Link>
)}
</Layout>
)
}
}
...
what I get of this is an error that says:
TypeError: Cannot read property 'edges' of undefined
ProductsSectionTemplate.componentDidMount
}
59 | componentDidMount(){
> 60 | const products = data.products.edges.map( product => ({
61 | ...product.node,
62 | previewImg: product.node.previewImg,
63 | category:product.node.category
here is the gatsby-node.js file:
const path = require('path')
exports.createPages = async ({graphql, actions}) => {
const { createPage } = actions
const products = await graphql(`
{
allProducts (sort: {fields: id, order: ASC}, limit: 100){
edges {
node {
id
slug
model
description
categories
}
}
}
}
`).then(result => {
if (result.errors) throw result.errors;
const productNodes = result.data.allProducts.edges;
return productNodes.map( edge =>{
return {
...edge.node
}
})
})
const productsPerPage = 6
const numPages = Math.ceil(products.length / productsPerPage);
Array.from({length:numPages}).forEach((_, i)=>{
createPage({
path: i === 0 ? '/products' : `/products/${i + 1}`,
component: path.resolve('./src/templates/ProductsSectionTemplate.js'),
context: {
products,
limit:productsPerPage,
skip: i * productsPerPage,
numPages,
currentPage: i + 1,
}
});
})
products.forEach( product => {
createPage({
path: `/${product.slug}`,
component: path.resolve('./src/templates/singleProduct.js'),
context: {
product
}
});
});
}
can anybody help me to get this working right..?
Thanks in advice, regards
One reason your query might come back as undefined could be that your graphQL variables do not contain any values.
You can only use variables inside your graphQL with page queries. For this you need to define context variables inside your gatsby-node.js as documented in the official documentation:
posts.forEach(({ node }, index) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/blog-post.js`),
// values in the context object are passed in as variables to page queries
context: {
title: node.passThisVariable,
},
})
})
If this does not answer your question, edit your question with your gatsby-node.js code.
Solved using hooks:
const ProductsSectionTemplate = ({ data }) => {
const [products, setProducts ] = useState([])
const [product, setProduct ] = useState('')
function handleChangeProduct (e) {
setProduct(e.target.value);
};
function getUnique(arr, comp) {
const unique = arr
//store the comparison values in array
.map(e => e[comp])
// store the keys of the unique objects
.map((e, i, final) => final.indexOf(e) === i && i)
// eliminate the dead keys & store unique objects
.filter(e => arr[e])
.map(e => arr[e]);
return unique;
}
useEffect(()=>{
const productsList = data.products.edges.map( product => ({
...product.node,
previewImg: product.node.previewImg,
category:product.node.category,
categories:product.node.categories
}))
setProducts(productsList)
},[data])
const uniqueProduct = getUnique(products, 'category')
const filterDropdown = products.filter(result => result.category === product)
return(
<Layout>
<h1>Productos</h1>
<h4 className="title">Filtrar Productos:</h4>
<div className="filters">
{uniqueProduct.map(product => (
<label key={product.id}>
<input
className='filters-available-size'
type="checkbox"
value={product.category}
onChange={handleChangeProduct}
/>
<span className="checkmark">
{product.category}
</span>
</label>
))}
</div>
{!!filterDropdown.length && (
<div>
<p>{product}</p>
<ProductsSection products={filterDropdown}/>
</div>
)}
{!!products.length && !filterDropdown.length && (
<div>
<p>Categorias</p>
<ProductsSection products={uniqueProduct}/>
</div>
)}
</Layout>
)
}
and also changed the query to just:
export const data = graphql`
{
products: allProducts{
edges {
node {
id
slug
categories
model
previewImg
description
category
}
}
}
}
`

Removing object from array using hooks (useState)

I have an array of objects. I need to add a function to remove an object from my array without using the "this" keyword.
I tried using updateList(list.slice(list.indexOf(e.target.name, 1))). This removes everything but the last item in the array and I'm not certain why.
const defaultList = [
{ name: "ItemOne" },
{ name: "ItemTwo" },
{ name: "ItemThree" }]
const [list, updateList] = useState(defaultList);
const handleRemoveItem = e => {
updateList(list.slice(list.indexOf(e.target.name, 1)))
}
return (
{list.map(item => {
return (
<>
<span onClick={handleRemoveItem}>x </span>
<span>{item.name}</span>
</>
)}
}
)
Expected behaviour: The clicked item will be removed from the list.
Actual behaviour: The entire list gets removed, minus the last item in the array.
First of all, the span element with the click event needs to have a name property otherwise, there will be no name to find within the e.target. With that said, e.target.name is reserved for form elements (input, select, etc). So to actually tap into the name property you'll have to use e.target.getAttribute("name")
Additionally, because you have an array of objects, it would not be effective to use list.indexOf(e.target.name) since that is looking for a string when you are iterating over objects. That's like saying find "dog" within [{}, {}, {}]
Lastly, array.slice() returns a new array starting with the item at the index you passed to it. So if you clicked the last-item, you would only be getting back the last item.
Try something like this instead using .filter(): codesandbox
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = () => {
const defaultList = [
{ name: "ItemOne" },
{ name: "ItemTwo" },
{ name: "ItemThree" }
];
const [list, updateList] = useState(defaultList);
const handleRemoveItem = (e) => {
const name = e.target.getAttribute("name")
updateList(list.filter(item => item.name !== name));
};
return (
<div>
{list.map(item => {
return (
<>
<span name={item.name} onClick={handleRemoveItem}>
x
</span>
<span>{item.name}</span>
</>
);
})}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can use Array.filter to do this in a one-liner:
const handleRemoveItem = name => {
updateList(list.filter(item => item.name !== name))
}
Eta: you'll also need to pass the name of your item in your onClick handler:
{list.map(item => {
return (
<>
<span onClick={() =>handleRemoveItem(item.name)}>x </span>
<span>{item.name}</span>
</>
)}
const defaultList = [
{ name: "ItemOne" },
{ name: "ItemTwo" },
{ name: "ItemThree" }
]
const [list, updateList] = useState(defaultList);
const handleRemoveItem = idx => {
// assigning the list to temp variable
const temp = [...list];
// removing the element using splice
temp.splice(idx, 1);
// updating the list
updateList(temp);
}
return (
{list.map((item, idx) => (
<div key={idx}>
<button onClick={() => handleRemoveItem(idx)}>x </button>
<span>{item.name}</span>
</div>
))}
)
Small improvement in my opinion to the best answer so far
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = () => {
const defaultList = [
{ name: "ItemOne" },
{ name: "ItemTwo" },
{ name: "ItemThree" }
];
const [list, updateList] = useState(defaultList);
const handleRemoveItem = (item) => {
updateList(list.filter(item => item.name !== name));
};
return (
<div>
{list.map(item => {
return (
<>
<span onClick={()=>{handleRemoveItem(item)}}>
x
</span>
<span>{item.name}</span>
</>
);
})}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Instead of giving a name attribute we just send it to the handle function
I think this code will do
let targetIndex = list.findIndex((each) => {each.name == e.target.name});
list.splice(targetIndex-1, 1);
We need to check name value inside object so use findIndex instead. then cut the object start from target index to 1 array after target index.
Codepen
From your comment your problem came from another part.
Change this view section
return (
<>
<span onClick={() => handleRemoveItem(item) }>x </span>
<span>{item.name}</span>
</>
)}
change function handleRemoveItem format
const handleRemoveItem = item => {
list.splice(list.indexOf(item)-1, 1)
updateList(list);
}
Redundant one liner - would not recommend as hard to test / type / expand / repeat / reason with
<button onClick={() => setList(list.slice(item.id - 1))}
A version without exports:
const handleDeleteItem = id => {
const remainingItems = list.slice(id - 1)
setList(remainingItems);
}
However I would consider expanding the structure of your logic differently by using helper functions in another file.
With that in mind, I made one example for filter and another for slice. I personally like the slice option in this particular use-case as it makes it easy to reason with. Apparently, it is also slightly more performant on larger lists if scaling (see references).
If using slice, always use slice not splice unless you have good reason not to do so as it adheres to a functional style (pure functions with no side effects)
// use slice instead of splice (slice creates a shallow copy, i.e., 'mutates' )
export const excludeItemFromArray = (idx, array) => array.slice(idx-1)
// alternatively, you could use filter (also a shallow copy)
export const filterItemFromArray = (idx, array) => array.filter(item => item.idx !== idx)
Example (with both options filter and slice options as imports)
import {excludeItemFromArray, filterItemFromArray} from 'utils/arrayHelpers.js'
const exampleList = [
{ id: 1, name: "ItemOne" },
{ id: 2, name: "ItemTwo" },
{ id: 3, name: "ItemThree" }
]
const [list, setList] = useState(exampleList);
const handleDeleteItem = id => {
//excluding the item (returning mutated list with excluded item)
const remainingItems = excludeItemFromArray(id, list)
//alternatively, filter item (returning mutated list with filtered out item)
const remainingItems = filterItemFromArray(id, list)
// updating the list state
setList(remainingItems);
}
return (
{list.map((item) => (
<div key={item.id}>
<button onClick={() => handleDeleteItem(item.id)}>x</button>
<span>{item.name}</span>
</div>
))}
)
References:
Don't use index keys in maps: https://robinpokorny.com/blog/index-as-a-key-is-an-anti-pattern/
Performance of slice vs filter: https://medium.com/#justintulk/javascript-performance-array-slice-vs-array-filter-4573d726aacb
Slice documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
Functional programming style: https://blog.logrocket.com/fundamentals-functional-programming-react/#:~:text=Functional%20programming%20codes%20are%20meant,computations%20are%20called%20side%20effects.
Using this pattern, the array does not jump, but we take the previous data and create new data and return it.
const [list, updateList] = useState([
{ name: "ItemOne" },
{ name: "ItemTwo" },
{ name: "ItemThree" }
]);
updateList((prev) => {
return [
...prev.filter((item, i) => item.name !== 'ItemTwo')
]
})
This is because both slice and splice return an array containing the removed elements.
You need to apply a splice to the array, and then update the state using the method provided by the hook
const handleRemoveItem = e => {
const newArr = [...list];
newArr.splice(newArr.findIndex(item => item.name === e.target.name), 1)
updateList(newArr)
}

Resources