Reputation: 97
I have an issue with my popup form in my note-taking project. I have a popup form that shows up if user wants to edit a note, and that popup should already have the contents of the note they chose to edit. The form is showing the correct contents, but also I noticed that when I click on "Edit", it takes two clicks for the note to get printed (I suspect that I'm using useState()
wrong but I can't pinpoint the exact spot where that's happening), and I'm wondering why that is. I've tried getting the data straight from the database, sending it to the form as props and , but I can't get to the desired result. I'm not sure where I'm going wrong and would appreciate any help!
Here's my Note component:
import axios from "axios";
import EditNote from './EditNote'
function Note(props) {
const [isEdit, setEditNote] = useState(false)
const [idToEdit, setIdToEdit] = useState('')
const [buttonPopup, setButtonPopup] = useState(false);
const [noteToEdit, setNoteToEdit] = useState({
title: "",
content: "",
category: ""
})
function deleteNote(id) {
axios.delete(`http://localhost:5000/notes/${id}`)
.then(() => { console.log("Note successfully deleted")
props.setFetch(true)
});
}
function changeNotes(id, title, content, category){
setEditNote(true)
setButtonPopup(true)
setNoteToEdit(prevNote => {
return {
...prevNote,
title : title,
content : content,
category : category
};
});
console.log(noteToEdit)
setIdToEdit(id)
console.log(idToEdit)
}
return (
<div>
{/* pass the notes array from CreateArea as props */}
{props.notes.map((noteItem) => {
return (
<div className="note">
<h1>{noteItem.title}</h1>
<p>{noteItem.content}</p>
<button onClick={() => {changeNotes(noteItem._id, noteItem.content, noteItem.title, noteItem.category)}}>
Edit
</button>
<button onClick={() => {deleteNote(noteItem._id)}}>
Delete
</button>
<p>{noteItem.category}</p>
</div>
);
})}
<EditNote trigger={buttonPopup} setButtonPopup={setButtonPopup} categories={props.categories} id={idToEdit} noteToEdit={noteToEdit} setEditNote={setEditNote} isEdit={isEdit}>
<h1>popup form</h1>
</EditNote>
</div>
)
}
export default Note
and EditNote component:
import axios from "axios";
export default function EditNote(props){
const [note, setNote] = useState({
title: props.noteToEdit.title,
content: props.noteToEdit.content,
});
function handleChange(event) {
const {name, value} = event.target;
setNote({...note, [name]: value});
}
const updateNote = () => {
setNote((prevValue, id) => {
if (props.noteToEdit._id === id) {
props.title = note.title;
props.content = note.content;
props.category = note.category
} else {
return { ...prevValue };
}
});
props.setEditNote(false);
props.setButtonPopup(false)
};
return (props.trigger) ? (
<div className="edit-notes-form">
<form className="popup-inner">
<input
name="title"
onChange={handleChange}
value={props.noteToEdit.title}
placeholder="Title"
/>
<textarea
name="content"
onChange={handleChange}
value={props.noteToEdit.content}
placeholder="Take a note..."
rows={3}
/>
<select
name="category"
onChange={handleChange}
value={props.noteToEdit.category}
>
{
props.categories.map(function(cat) {
return <option
key={cat.category} value={cat.value} > {cat.category} </option>;
})
}
</select>
<div className="btn-update-note">
<button className="btn-update" onClick={updateNote}>Save</button>
<button className="btn-update" onClick={() => props.setButtonPopup(false)}>Close</button>
</div>
</form>
</div> ) : ''
}
I also tried useRef, but it's not working as intended. Would appreciate any direction on this, thank you!
Upvotes: 0
Views: 517
Reputation: 97
After tweaking the code some more the update note functionality is working as intended, I'm sure the code isn't nearly perfect, but maybe it helps someone! Here's what I ended up with:
EditNote component:
import axios from "axios";
export default function EditNote(props){
const [note, setNote] = useState({
title: props.noteToEdit.title,
content: props.noteToEdit.content
});
console.log(props.noteToEdit)
function handleChange(event) {
event.preventDefault();
const {name, value} = event.target;
setNote({...note, [name]: value});
}
const updateNote = (event) => {
event.preventDefault();
if (!note.title || !note.content) {
alert("'Title' or 'Content' cannot be blank");
} else {
axios.post(`http://localhost:5000/notes/update/${props.id}`, note)
.then((res) =>{
console.log("Note updated successfully")
props.setFetch(true)
})
.catch(err => console.log("Error: " + err));
}
props.setButtonPopup(false)
};
return (props.trigger) ? (
<div className="edit-notes-form">
<form className="popup-inner">
<input
name="title"
onChange={handleChange}
defaultValue={props.noteToEdit.title}
placeholder="Title"
/>
<textarea
name="content"
onChange={handleChange}
defaultValue={props.noteToEdit.content}
placeholder="Take a note..."
rows={3}
/>
<select
name="category"
onChange={handleChange}
defaultValue={props.noteToEdit.category}
>
{
props.categories.map(function(cat) {
return <option
key={cat.category} value={cat.value} > {cat.category} </option>;
})
}
</select>
<div className="btn-update-note">
<button className="btn-update" onClick={updateNote}>Save</button>
<button className="btn-update" onClick={() => props.setButtonPopup(false)}>Close</button>
</div>
</form>
</div> ) : ''
}
and Note component:
import axios from "axios";
import EditNote from './EditNote'
function Note(props) {
const [idToEdit, setIdToEdit] = useState('')
const [buttonPopup, setButtonPopup] = useState(false);
const [noteToEdit, setNoteToEdit] = useState({
title: "",
content: "",
category: ""
})
function deleteNote(id) {
axios.delete(`http://localhost:5000/notes/${id}`)
.then(() => { console.log("Note successfully deleted")
props.setFetch(true)
});
}
function changeNotes(id, title, content, category){
setButtonPopup(true)
setNoteToEdit(prevNote => {
return {
...prevNote,
title : title,
content : content,
category : category
};
});
console.log(noteToEdit)
setIdToEdit(id)
console.log(idToEdit)
}
return (
<div>
{/* pass the notes array from CreateArea as props */}
{props.notes.map((noteItem) => {
return (
<div className="note">
<h1>{noteItem.title}</h1>
<p>{noteItem.content}</p>
<button onClick={() => {changeNotes(noteItem._id, noteItem.title, noteItem.content, noteItem.category)}}>
Edit
</button>
<button onClick={() => {deleteNote(noteItem._id)}}>
Delete
</button>
<p>{noteItem.category}</p>
</div>
);
})}
<EditNote trigger={buttonPopup} setButtonPopup={setButtonPopup} categories={props.categories} id={idToEdit} noteToEdit={noteToEdit} setFetch={props.setFetch}>
</EditNote>
</div>
)
}
export default Note```
Upvotes: 0
Reputation: 11600
As https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function states:
Any function inside a component, including event handlers and effects, “sees” the props and state from the render it was created in.
so, in your code, changeNotes
will log previous value of noteToEdit
, not the current one. When you click Edit
once more, this time the previous value and the current value is the same so it seems as if it's working as you imagine, but it's not. It will always log the stale value but it sometimes happens that previous value is still valid as when you click twice on Edit
.
Upvotes: 2
Reputation: 1
setNoteToEdit is async therefore you're going to see the previous value in the console.log.
Also inside of setNote, in EditNote.tsx, you should pass as a prop the setNoteToEdit and use that to change the note to edit. That way the state change in the parent will trigger a render with the new values.
Upvotes: 0