01nowicj
01nowicj

Reputation: 97

Form takes two clicks to update in react

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

Answers (3)

01nowicj
01nowicj

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

marzelin
marzelin

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

John Retsas
John Retsas

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

Related Questions