I’m building a to-do app in React with a Node.js/Express backend and PostgreSQL as my database. The app allows users to add, edit, and delete tasks, which are stored in the database.
The problem I’m facing is with editing tasks: when I submit an edit, the change doesn’t display immediately on the page. The edited task only shows correctly if I manually refresh the page. I’d like to understand why this isn’t updating in real-time and how to fix it.
App.js (React Component) :
import React, { useEffect, useState } from "react";
import './App.css';
import Header from "./components/Header";
import AddTask from "./components/AddTask";
import ListNote from "./components/ListNote";
import axios from 'axios';
function App() {
const [isFormVisible, setIsFormVisible]= useState(false);
const[notes, setNotes]= useState([]);
const [noteToEdit, setNoteToEdit] = useState(null);
useEffect(() => {
const fetchNotes = async () => {
try {
const response = await axios.get('http://localhost:8000/todos');
setNotes(response.data); // Set the notes from the backend response
console.log("Fetched tasks:", response.data);
} catch (error) {
console.error('Error fetching notes:', error);
}
};
fetchNotes(); // Call fetchNotes when the component loads
}, []);
const handleAddNewTask = () => {
setIsFormVisible(true); // Show the form when button is clicked
setNoteToEdit(null); // Reset noteToEdit when adding a new task
};
const handleCloseForm = () => {
setIsFormVisible(false);
};
// 2. Add new note to backend (POST Request)
async function addNote(note) {
try {
if (noteToEdit !== null) {
// Edit mode: update the existing note
const response = await axios.put(`http://localhost:8000/todos/${noteToEdit.id}`, note);
setNotes(prevNotes =>
prevNotes.map((noteItem) =>
noteItem.id === noteToEdit.id ? response.data : noteItem
)
);
setNoteToEdit(null); // Reset edit mode
} else {
// Add new note (POST)
const response = await axios.post('http://localhost:8000/todos', note);
const newNote = response.data;
setNotes(prevNotes => [...prevNotes, newNote]);
}
} catch (error) {
console.error('Error adding/updating note:', error);
}
setIsFormVisible(false); // Close the form after submit
}
async function deleteNote(id) {
try {
await axios.delete(`http://localhost:8000/todos/${id}`);
setNotes(prevNotes => prevNotes.filter((noteItem) => noteItem.id !== id));
} catch (error) {
console.error('Error deleting note:', error);
}
}
function editNote(id) {
setIsFormVisible(true);
const noteToEdit = notes.find((noteItem) => noteItem.id === id);
setNoteToEdit(noteToEdit); // Store the note to edit it later
}
const handleDisappear = () => {
setIsFormVisible(false);
};
return (
<div className="app">
<Header onAddNewTask={handleAddNewTask}/>
{isFormVisible && <AddTask onSubmit={handleDisappear} onAdd={addNote} onClose={handleCloseForm} note={noteToEdit} />}
{notes.map((noteItem) => {
return <ListNote
key={noteItem.id}
id= {noteItem.id}
title= {noteItem.title}
onEdit={() => editNote(noteItem.id)} // Passing index for edit
onDelete={() => deleteNote(noteItem.id)}
/>;
}) }
</div>
);
}
export default App;
Server.js
const PORT = process.env.PORT ?? 8000
const express = require('express')
const cors = require('cors')
const app = express()
const pool = require('./db')
app.use(cors());
app.use(express.json());
app.get ('/todos', async (req,res) => {
try{
const todos = await pool.query('SELECT * from todos')
res.json(todos.rows)
} catch (err) {
console.error(err);
}
})
app.post ('/todos', async (req,res) => {
try {
const { title, content }= req.body;
if (!title || !content) {
return res.status(400).json({ error: "Title and description are required." });
}
const newTodo = await pool.query('INSERT INTO todos (title, content) VALUES($1, $2) RETURNING *', [title, content]);
res.json(newTodo.rows[0]);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
})
// PUT request to update a todo
app.put('/todos/:id', async (req, res) => {
try {
const { id } = req.params; // Get todo ID from the URL
const { title, content } = req.body; // Destructure data from request body
await pool.query(
'UPDATE todos SET title = $1, content = $2 WHERE id = $3',
[title, content, id]
);
res.json('Todo updated successfully!');
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
});
// DELETE request to remove a todo
app.delete('/todos/:id', async (req, res) => {
try {
const { id } = req.params; // Get todo ID from the URL
await pool.query('DELETE FROM todos WHERE id = $1', [id]);
res.json('Todo deleted successfully!');
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
});
app.listen(PORT, ()=> console.log (`server running on PORT ${PORT}`) )
Screenshot :
2
Answers
In your addNot function you are not returning the new list returned by the map. Please add the "return" keyword to your map like this:
There is an array mutation in the code, perhaps it may be the whole issue.
The following code has the issue. The code finds the object and then stores a reference to it in the variable noteToEdit. It then passed to the state updater function setNoteToEdit. Therefore the object referenced in the state noteToEdit is still the same object in the state Notes. And any changes to noteToEdit will cause the same object in the state Notes to be mutated, which eventually results the array mutation.
Solution : Please copy the object as below which will delink the unintended reference.