Search code examples
javascriptreactjsgoogle-keep

edit notes on a Google Keep clone app with React js


I am building a clone of the Google Keep app with react js. I added all the basic functionality (expand the create area, add a note, delete it) but I can't seem to manage the edit part. Currently I am able to edit the inputs and store the values in the state, but how can I replace the initial input values for the new values that I type on the input?

This is Note component

export default function Note(props) {
  const [editNote, setEditNote] = useState(false);
  const [currentNote, setCurrentNote] = useState({
    id: props.id,
    editTitle: props.title,
    editContent: props.content,
  });

  const handleDelete = () => {
    props.deleteNote(props.id);
  };

  const handleEdit = () => {
    setEditNote(true);
    setCurrentNote((prevValue) => ({ ...prevValue }));
  };

  const handleInputEdit = (event) => {
    const { name, value } = event.target;

    setCurrentNote((prevValue) => ({
      ...prevValue,
      [name]: value,
    }));
  };

  const updateNote = () => {
    setCurrentNote((prevValue, id) => {
      if (currentNote.id === id) {
        props.title = currentNote.editTitle;
        props.content = currentNote.editContent;
      } else {
        return { ...prevValue };
      }
    });
    setEditNote(false);
  };

  return (
    <div>
      {editNote ? (
        <div className='note'>
          <input
            type='text'
            name='edittitle'
            defaultValue={currentNote.editTitle}
            onChange={handleInputEdit}
            className='edit-input'
          />
          <textarea
            name='editcontent'
            defaultValue={currentNote.editContent}
            row='1'
            onChange={handleInputEdit}
            className='edit-input'
          />
          <button onClick={() => setEditNote(false)}>Cancel</button>
          <button onClick={updateNote}>Save</button>
        </div>
      ) : (
        <div className='note' onDoubleClick={handleEdit}>
          <h1>{props.title}</h1>
          <p>{props.content}</p>
          <button onClick={handleDelete}>DELETE</button>
        </div>
      )}
    </div>
  );
}

And this is the Container component where I am renderind the CreateArea and mapping the notes I create. I tried to map the notes again with the new values but it wasn't working.

export default function Container() {
  const [notes, setNotes] = useState([]);

  const addNote = (newNote) => {
    setNotes((prevNotes) => {
      return [...prevNotes, newNote];
    });
  };

  const deleteNote = (id) => {
    setNotes((prevNotes) => {
      return prevNotes.filter((note, index) => {
        return index !== id;
      });
    });
  };

  // const handleUpdateNote = (id, updatedNote) => {
  //   const updatedItem = notes.map((note, index) => {
  //     return index === id ? updatedNote : note;
  //   });
  //   setNotes(updatedItem);
  // };

  return (
    <div>
      <CreateArea addNote={addNote} />
      {notes.map((note, index) => {
        return (
          <Note
            key={index}
            id={index}
            title={note.title}
            content={note.content}
            deleteNote={deleteNote}
            //handleUpdateNote={handleUpdateNote}
          />
        );
      })}
    </div>
  );
}

Solution

  • There are a couple of mistakes in your code.

    1. The state properties are in the camel case
      const [currentNote, setCurrentNote] = useState({
        ...
        editTitle: props.title,
        editContent: props.content,
      });
    

    But the names of the input are in lowercase.

              <input
                name='edittitle'
                ...
              />
              <textarea
                name='editcontent'
                ...
              />
    

    Thus in handleInputEdit you don't update the state but add new properties: edittitle and editcontent. Change the names to the camel case.

    1. In React you cant assign to the component prop values, they are read-only.
      const updateNote = () => {
        ...
            props.title = currentNote.editTitle;
            props.content = currentNote.editContent;
    

    You need to use the handleUpdateNote function passed by the parent component instead. You have it commented for some reason.

              <Note
                ...
                //handleUpdateNote={handleUpdateNote}
              />
    

    Check the code below. I think it does what you need.

    function Note({ id, title, content, handleUpdateNote, deleteNote }) {
      const [editNote, setEditNote] = React.useState(false);
      const [currentNote, setCurrentNote] = React.useState({
        id,
        editTitle: title,
        editContent: content,
      });
    
      const handleDelete = () => {
        deleteNote(id);
      };
    
      const handleEdit = () => {
        setEditNote(true);
        setCurrentNote((prevValue) => ({ ...prevValue }));
      };
    
      const handleInputEdit = (event) => {
        const { name, value } = event.target;
        setCurrentNote((prevValue) => ({
          ...prevValue,
          [name]: value,
        }));
      };
    
      const updateNote = () => {
        handleUpdateNote({
          id: currentNote.id,
          title: currentNote.editTitle,
          content: currentNote.editContent
        });
        setEditNote(false);
      };
    
      return (
        <div>
          {editNote ? (
            <div className='note'>
              <input
                type='text'
                name='editTitle'
                defaultValue={currentNote.editTitle}
                onChange={handleInputEdit}
                className='edit-input'
              />
              <textarea
                name='editContent'
                defaultValue={currentNote.editContent}
                row='1'
                onChange={handleInputEdit}
                className='edit-input'
              />
              <button onClick={() => setEditNote(false)}>Cancel</button>
              <button onClick={updateNote}>Save</button>
            </div>
          ) : (
            <div className='note' onDoubleClick={handleEdit}>
              <h1>{title}</h1>
              <p>{content}</p>
              <button onClick={handleDelete}>DELETE</button>
            </div>
          )}
        </div>
      );
    }
    
    function CreateArea() {
      return null;
    }
    
    function Container() {
      const [notes, setNotes] = React.useState([
        { title: 'Words', content: 'hello, bye' },
        { title: 'Food', content: 'milk, cheese' }
      ]);
    
      const addNote = (newNote) => {
        setNotes((prevNotes) => {
          return [...prevNotes, newNote];
        });
      };
    
      const deleteNote = (id) => {
        setNotes((prevNotes) => {
          return prevNotes.filter((note, index) => {
            return index !== id;
          });
        });
      };
    
      const handleUpdateNote = ({ id, title, content }) => {
        const _notes = [];
        for (let i = 0; i < notes.length; i++) {
          if (i === id) {
            _notes.push({ id, title, content });
          } else {
            _notes.push(notes[i]);
          }
        }
        
        setNotes(_notes);
      };
    
      return (
        <div>
          <CreateArea addNote={addNote} />
          {notes.map((note, index) => {
            return (
              <Note
                key={index}
                id={index}
                title={note.title}
                content={note.content}
                deleteNote={deleteNote}
                handleUpdateNote={handleUpdateNote}
              />
            );
          })}
        </div>
      );
    }
    
    
    function App() {
      return (
        <div>
          <Container />
        </div>
      );
    }
    
    ReactDOM.render(
      <App />,
      document.getElementById('root')
    );
    <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
    <div id="root"></div>

    Also, you can store the notes in an object or hash map instead of an array. For example

         const [notes, setNotes] = React.useState({
            'unique_id': { title: 'Words', content: 'hello, bye' }
          });
    

    Then in handleUpdateNote you have

    setNotes((prev) => ({ ...prev, unique_id: { title, content } }))