I want to render a list of rows that can be added to and deleted dynamically. I have 2 components that handle adding and removing rows: ClassComponent
and FunctionComponent
. ClassComponent
works as intended, but FunctionComponent
only deletes the first row. The rows are stored as state and are updated using setState(). How can I make the function component logically equivalent to the class component?
I'm using the library uuid
to create unique keys.
import React from "react";
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
export default function App() {
return (
<div>
<ClassComponent />
<FunctionComponent />
</div>
);
}
class ClassComponent extends React.Component {
state = {
list: [{ id: uuidv4() }, { id: uuidv4() }],
};
handleDelete = (id) => {
this.setState((prevState) => ({
list: prevState.list.filter((row) => row.id !== id),
}));
};
handleAdd = () => {
this.setState((prevState) => ({
list: [...prevState.list, { id: uuidv4() }],
}));
};
render() {
const { list } = this.state;
return (
<>
<ul>
{list.map(({ id }) => (
<Row key={id} id={id} onClick={this.handleDelete}>
{id}
</Row>
))}
</ul>
<button onClick={this.handleAdd}>Add</button>
</>
);
}
}
const FunctionComponent = () => {
const [list, setList] = useState([{ id: uuidv4() }, { id: uuidv4() }]);
const handleDelete = (id) => {
console.log(id);
const copy = list.slice();
copy.splice(id, 1);
setList(copy);
};
const handleAdd = () => {
setList([...list, { id: uuidv4() }]);
};
return (
<>
<ul>
{list.map(({ id }) => (
<Row key={id} id={id} onClick={handleDelete}>
{id}
</Row>
))}
</ul>
<button onClick={handleAdd}>Add</button>
</>
);
};
const Row = ({ onClick, children, id }) => (
<li>
{children} <button onClick={() => onClick(id)}>Delete</button>
</li>
);
CodePudding user response:
Is there any reason not to use array.prototype.filter()
just like you've done in your Class Component? This seems more readable and ensures you avoid mutating state directly in fewer steps.
Here is what that would look like in your handleDelete
function in FunctionComponent
:
const handleDelete = (id) => {
setList(list.filter((row) => (
row.id !== id
)));
};
CodePudding user response:
You have to review your usage of the splice
Array prototype.
copy.splice(id, 1);
Splice need to be passed as first argument an index and at second argument a delete count as such:
splice(start, deleteCount)
and you are passing an id to it. Actually in your case you are passing undefined as you're calling the function without argument. I guess you could use the index of the map function to make it work:
{list.map(({ id }, index) => (
<Row key={id} id={id} onClick={() => handleDelete(index)}>
{id}
</Row>
))}
In your case you were passing undefined as id and 1 as the deleteCount so it was always deleting the first item.
CodePudding user response:
In your FunctionalComponent
you need to give index of the entry to splice
method. Try like below.
const handleDelete = (id) => {
const copy = list.slice();
// find the index
const index = copy.findIndex(({ id: ID }) => id === ID);
// do the deletiong using that index
copy.splice(index, 1);
setList(copy);
};
CodePudding user response:
in the functional component, you did not pass the id!
const FunctionalComponent = () => {
const [list, setList] = useState([{ id: uuidv4() }, { id: uuidv4() }]);
const handleDelete = (id) => {
// get all object except the id
const newRows = list.filter((i) => i.id !== id);
setList(newRows);
};
const handleAdd = () => {
setList([...list, { id: uuidv4() }]);
};
return (
<>
<ul>
{list.map(({ id }) => (
<Row key={id} id={id} onClick={() => handleDelete(id)}>
{id}
</Row>
))}
</ul>
<button onClick={handleAdd}>Add</button>
</>
);
};