Reputation: 85
I'm trying to build a todo page where I can input todos in my input field. All todos will be rendered below. I managed to build a form where I can type in a todo title and send it to my database. A small problem I'm having here is that I need to refresh the page after pushing the add button to see the new list. I assume this is because I use componentDidMount and this updates only at page refresh. Any idea how I can do this at page refresh (componentDidUpdate) AND at state change ?
FRONT-END
import React from 'react'
import './Todo.css'
import Todoitem from '../components/Todoitem'
import axios from 'axios'
import qs from "qs"
import DefaultLayout from "../layout/Default"
class Todo extends React.Component {
constructor() {
super()
this.state = {
title:"",
todos:[]
}
this.handleChange=this.handleChange.bind(this)
this.handleSubmit=this.handleSubmit.bind(this)
}
componentDidMount(){
axios({
method: "GET",
url: `${process.env.REACT_APP_API_BASE}/todo`,
withCredentials: true
})
.then(response => {
console.log(response)
let todolist = response.data;
this.setState({todos:todolist})
})
.catch(error => {
console.log("You've made an error when getting the todos charles: ",error)
})
}
handleChange(event){
event.preventDefault()
let name = event.target.name
let value = event.target.value
this.setState({
[name]:value
})
console.log(this.state.title)
}
handleSubmit(event){
event.preventDefault()
if (!this.state.title) {
debugger
}
axios({
method: "POST",
url: `${process.env.REACT_APP_API_BASE}/todo`,
data: qs.stringify({title: this.state.title}),
headers: {"content-type": "application/x-www-form-urlencoded"},
withCredentials: true
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error.response)
})
}
handleDelete(todoId){
axios
.delete(`${process.env.REACT_APP_API_BASE}/todo/${todoId}`)
.then(response => {
const remainingTodos = this.state.todos.filter(element => element._id !== todoId)
this.setState({
todos: remainingTodos
})
})
.catch(err => console.log(err))
}
render() {
return (
<div>
<DefaultLayout>
<h1>To-do things for this app</h1>
<h2 className="todotitle">Add your to-do here, Charles!</h2>
<form className="todocontainer" onClick={this.handleSubmit}>
<div className="inputbuttonandfield">
<div className="inputcontainer">
<div className="captionpart">
<label className="captionlabel" htmlFor="title">Add to-do:</label><br></br>
<input className="captionform" type="text" name="title" value={this.state.title} placeholder="Type your to-do here!" onChange={(e) => this.handleChange(e)}></input>
<button className="shootbutton">Add!</button>
</div>
</div>
</div>
</form>
{
this.state.todos.map(element=> (
<div className="todosoverviewlister" key={element._id}>
<Todoitem id={element._id} title={element.title} />
<button className="tododelete" onClick={()=> this.handleDelete(element._id)}>Delete</button>
</div>
))
}
</DefaultLayout>
</div>
)
}
}
export default Todo
Todomodel
const mongoose = require("mongoose")
const Schema = mongoose.Schema
const todoSchema = new Schema({
title: String
})
const Todo = mongoose.model("todos",todoSchema)
module.exports = Todo
BACKEND
//request todos
router.get("/todo", (req,res) => {
Todo
.find()
.then(response => {
res.json(response)
})
.catch(error => {
res.json(error)
})
})
//delete todo
router.delete("/todo/:id", (req,res)=>{
Todo
.findByIdAndDelete(req.params.id)
.then(response => {
res.json(response)
})
.catch(error => {
res.json(error)
})
})
Upvotes: 1
Views: 746
Reputation: 2866
You can either update the state or sync up with database by sending another GET. Let me break it down into 2 solutions:
Just update the state
// you code ...
handleSubmit(event){
event.preventDefault()
const newTodo = { title: this.state.title }; // extract your todo into const
axios({
method: "POST",
url: `${process.env.REACT_APP_API_BASE}/todo`,
data: qs.stringify(newTodo), // send todo in the POST
headers: {"content-type": "application/x-www-form-urlencoded"},
withCredentials: true
})
.then((response) => {
console.log(response)
this.setState(prevState => ({ // immutably update the state
todos: [...prevState.todos, newTodo]
}));
})
.catch((error) => {
console.log(error.response)
})
}
// your code ...
Send GET after POST:
// your Todo component
class Todo extends React.Component {
constructor() {
super();
this.state = {
title: "",
todos: [],
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// extract method for loading TODOs (your previous componentDidMount)
loadTodos = () => {
axios({
method: "GET",
url: `${process.env.REACT_APP_API_BASE}/todo`,
withCredentials: true,
})
.then((response) => {
console.log(response);
let todolist = response.data;
this.setState({ todos: todolist });
})
.catch((error) => {
console.log(
"You've made an error when getting the todos charles: ",
error
);
});
}
componentDidMount() {
this.loadTodos(); // use the extracted method
}
handleChange(event) {
event.preventDefault();
let name = event.target.name;
let value = event.target.value;
this.setState({
[name]: value,
});
console.log(this.state.title);
}
handleSubmit(event) {
event.preventDefault();
if (!this.state.title) {
debugger;
}
axios({
method: "POST",
url: `${process.env.REACT_APP_API_BASE}/todo`,
data: qs.stringify({ title: this.state.title }),
headers: { "content-type": "application/x-www-form-urlencoded" },
withCredentials: true,
})
.then((response) => {
console.log(response);
this.loadTodos(); // use the extracted method
})
.catch((error) => {
console.log(error.response);
});
}
handleDelete(todoId) {
axios
.delete(`${process.env.REACT_APP_API_BASE}/todo/${todoId}`)
.then((response) => {
const remainingTodos = this.state.todos.filter(
(element) => element._id !== todoId
);
this.setState({
todos: remainingTodos,
});
})
.catch((err) => console.log(err));
}
render() {
return (
<div>
<DefaultLayout>
<h1>To-do things for this app</h1>
<h2 className="todotitle">Add your to-do here, Charles!</h2>
<form className="todocontainer" onClick={this.handleSubmit}>
<div className="inputbuttonandfield">
<div className="inputcontainer">
<div className="captionpart">
<label className="captionlabel" htmlFor="title">
Add to-do:
</label>
<br></br>
<input
className="captionform"
type="text"
name="title"
value={this.state.title}
placeholder="Type your to-do here!"
onChange={(e) => this.handleChange(e)}
></input>
<button className="shootbutton">Add!</button>
</div>
</div>
</div>
</form>
{this.state.todos.map((element) => (
<div className="todosoverviewlister" key={element._id}>
<Todoitem id={element._id} title={element.title} />
<button
className="tododelete"
onClick={() => this.handleDelete(element._id)}
>
Delete
</button>
</div>
))}
</DefaultLayout>
</div>
);
}
}
export default Todo;
Upvotes: 2
Reputation: 186
I believe the issue is that you're not updating the state when you submit (during the add operation). In your delete, you correctly keep the list in state synced with the list on the server by removing the element locally as well. In the add, you should to something similar by adding the new element to the list in state (or more precisely, make a deep copy and overwrite the one in state). That should do it.
There is no need to refetch the entire list from the server unless there are multiple users operating on the same list. If that's the case, you can add a get() call in the response of your submit. As long as the response of that operation writes to state, it will update correctly. But again, avoid that unless you need it as it will make your app slower and less responsive.
Upvotes: 2