Search code examples
arraysreactjsreact-hooksstateprop

How to use a useState function in another component with props?


I write a React.js note web application where a user can add up to 10 notes.

I use map() to iterate the array of notes, and a useState(1) hook to update its count (the default number of notes is 1), so I would like to do something like this:

  {[...Array(noteCount)].map((_, i) => <Note onUpdateNoteCount={() =>setNoteCount(n => n - 1)} key={i} />)}

The thing is that the Note() component is inside a Main() component which is in the App() component, so I want to get the needed values as props of App(), and than use them in Note(), but can not figure out how and where to put it.

Thanks!

App.js

  import React from 'react';
  import Header from './Header';
  import Main from './Main';
function App () {
  const [noteCount, setNoteCount] = React.useState(1);
  function multiplyNoteComponent () {
    if (noteCount < 20) {
      setNoteCount(n => n + 1)
    }
    else {
      alert('too many notes. remove or combine some of them together!')
    }
  }
  return (
    <div>
      <Header/>
      {[...Array(noteCount)].map((_, i) => <Main onUpdateNoteCount={() =>setNoteCount(n => n - 1)} key={i} />)}

      <button
            style={{left: '5%'}}
          id='addNoteBtn'
          onClick={multiplyNoteComponent}
          title='Add a note'
         >
        +
      </button>
    </div>
  );
}
  export default App;

Main.js

    import React from 'react';
    import Note from './Note';
function Main () {
    return (
        <main>
            your notes are:
            <Note/>
        </main>
    )
}
    export default Main;

Note.js

    import React from 'react';
function Note () {
    return (
        <div> <button title='delete note' onClick={}>X</delete>
            <li>
                <input type='text'/>
            </li>
        </div>
    )
}
    export default Note

Edit: the reason I think I need the setNoteCount() function to be used in the Note() component, is for the count down when a note is being deleted (every note has its own delete button).


Solution

  • I would recommend this architecture of the your App.

    1. Store the Notes array at the App level.
    2. Add a note using NoteInput which adds a notes to your Notes array.
    3. Map your Notes using the Note component which takes onDelete as a prop from App level.
    4. Your App component should be responsible for storing and delete a note from the state.

    In your example, notesCount is meant to a derivative state. i.e it could be derived simply from the Notes array (notes.length).

    So, rather than storing notesCount, I recommend storing notes and deriving count from it.

    You could see the working example here :- https://stackblitz.com/edit/react-g19tei

    import React from "react";
    import "./style.css";
    
    const NOTES_ALLOWED = 10;
    
    export default function App() {
      const [notes, setNotes] = React.useState([]);
    
      function addNote(newNote) {
        if (notes.length === NOTES_ALLOWED) {
          alert(`Only ${NOTES_ALLOWED} notes are allowed to be added`)
        } else {
          setNotes([...notes, newNote]);
        }
      }
    
      function handleDelete(deleteNoteIdx) {
        const newNotes = [...notes];
        // delete the note at the specific index
        newNotes.splice(deleteNoteIdx, 1)
        setNotes(newNotes);
      }
    
      return (
        <div>
          <div style={{ marginTop: 20, marginBottom: 20 }}>
            <p>Your notes are</p>
            {notes.map((note, idx) => ( 
              <Note 
                note={note} 
                onDelete={() => handleDelete(idx)} 
              />
            ))}
          </div>
          <NoteInput onAdd={addNote} />
        </div>
      );
    }
    
    function Note({ note, onDelete }) {
      return (
        <div>
         <p>{note}
          <button onClick={onDelete}>Delete Note</button>
         </p>
        </div>
      )
    }
    
    function NoteInput({ onAdd }) {
      const [note, setNote] = React.useState('');
    
      function handleSubmit(e) {
        e.preventDefault();
        const noteToBeSend = note;
        setNote('')
        onAdd(noteToBeSend.trim());
      }
    
      return (
        <div>
         <form onSubmit={handleSubmit}>
            <input 
              type="text" 
              value={note}
              onChange={e => setNote(e.target.value)}
              required
            />
            <button type="submit">Add Note</button>
          </form>
        </div>
      )
    }