Reputation: 1865
I've been following one of the MERN stack tutorials online (making a simple todo app), and decided to go off-script a little bit. I wanted to add a button to delete a specific item. The delete function is working fine, however it requires the user to manually refresh the page after they click the delete button in order to see the new list of elements in my database (MongoDB). I'd like the page to automatically refresh after the click event, however I'm not sure where to start. Within the react render there is a table, which references a variable to actually assemble the components of the table - this is where the delete button exists. Here is my code:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
const Todo = props => (
<tr>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_title}</td>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_description}</td>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_responsible}</td>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_priority}</td>
<td>
<Link to={"/edit/"+props.todo._id}>Edit</Link>
</td>
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(console.log("Deleted: " + props.todo._id))
.catch(err => console.log(err))
}
>Delete</button>
</td>
</tr>
)
export default class TodosList extends Component {
constructor(props) {
super(props);
this.state = {todos: []};
}
componentDidMount() {
axios.get('http://localhost:4000/todos/')
.then(res => {
this.setState({ todos: res.data });
})
.catch(function(err){
console.log(err);
})
}
todoList() {
return this.state.todos.map(function(currentTodo, i){
return <Todo todo={currentTodo} key={i} />;
})
}
render() {
return (
<div>
<h3>Todos List</h3>
<table className="table table-striped" style={{ marginTop: 20 }} >
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Responsible</th>
<th>Priority</th>
<th>Action</th>
<th>Remove Item</th>
</tr>
</thead>
<tbody>
{ this.todoList() }
</tbody>
</table>
</div>
)
}
}
Hopefully someone on here can get me pointed in the right direction.
Thanks
Upvotes: 1
Views: 11095
Reputation: 2473
You can delete the specific item from TodosList
component state after you have successfully deleted the item from Todo
component. For that you can
1) add a method in TodosList
component.
deleteItemHandler = (id) => {
const updatedTodos = this.state.todos.filter(todo => todo.id !== id);
this.setState({todos: updatedTodos})
}
2) pass the method deleteItemHandler
as props to Todo
component
todoList() {
return this.state.todos.map((currentTodo, i) => {
return <Todo todo={currentTodo} deleteItem={this.deleteItemHandler} key={i} />;
})
}
3) use it after item is successfully deleted
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(() => props.deleteItem(props.todo._id))
.catch(err => console.log(err))
}
>Delete</button>
</td>
Another way
Instead deleting item from TodosList
component you can also update the state. For that you can
1) add method that updates in TodosList
component
updateStateHandler = () => {
axios.get('http://localhost:4000/todos/')
.then(res => {
this.setState({ todos: res.data });
})
.catch(function(err){
console.log(err);
})
}
2) pass the method updateStateHandler
as props to Todo
component
todoList() {
return this.state.todos.map((currentTodo, i) => {
return <Todo todo={currentTodo} updateState={this.updateStateHandler} key={i} />;
})
}
3) use it after item is successfully deleted
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(() => props.updateState())
.catch(err => console.log(err))
}
>Delete</button>
</td>
Upvotes: 4
Reputation: 4285
You need to do this
export default class TodosList extends Component {
constructor(props) {
super(props);
this.state = {todos: []};
this.fetchTodos = this.fetchTodos.bind(this);
}
fetchTodos() {
axios.get('http://localhost:4000/todos/')
.then(res => {
this.setState({ todos: res.data });
})
.catch(function(err){
console.log(err);
});
}
componentDidMount() {
this.fetchTodos();
}
todoList() {
return this.state.todos.map((currentTodo, i) => {
return <Todo todo={currentTodo} fetchTodos={this.fetchTodos} key={i} />;
})
}
...
Todo:
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(() => {
console.log("Deleted: " + props.todo._id);
props.fetchTodos();
})
.catch(err => console.log(err));
}
>Delete</button>
</td>
Upvotes: 1
Reputation: 53578
The authority that renders the table is your TodosList
class, so it needs to be told to do the deleting:
class TodosList extends ... {
...
todoList() {
return this.state.todos.map((currentTodo, i) => {
let onDelete = () => {
this.removeItem(i);
};
// NEVER use an array position as key. The key is meant to uniquely
// identify the _item itself_ and is used in DOM diffing. Moving elements
// inside an array does not change those elements in the slightest and only
// requires moving DOM nodes around, but if you use array position as key,
// what you've now done is said that _everything in the DOM node has changed_
// So: don't do that. Use a real, item based, value.
return <Todo todo={currentTodo} key={currentTodo.id} onDelete={onDelete}/>;
// Of course this assumes todo items have an `id` property.
// If they don't, pick another property _on the todo item_ that
// uniquely identifies it.
});
}
removeItem(i) {
let todos = this.state.todos;
todos.splice(i,1);
// This single call now results in all the UI updates that you
// need to have happen: the todo item is no longer in the state,
// and so its DOM node will be removed from the page. And because
// we're now using real keys, React will not touch any of the other
// DOM nodes. The UI update is near-instant.
this.setState({ todos });
}
...
}
Then the individual buttons can call their own onDelete once deleting has happened:
const deleteThisItem = () => {
axios
.delete('http://localhost:4000/todos/'+props.todo._id)
.then(this.props.onDelete())
.catch(err => console.log(err))
};
<button onClick={deleteThisItem}>delete</button>
So the flow is:
setState
with that change, React will render what needs to be rendered.Upvotes: 0