How to avoid prop drilling ? / How to use useContext?

I'm working on a React Notes Application and my App.js contains all the necessary functions props which are passed down to several components.

As a result I'm doing prop drilling a lot where I'm passing down around 10-20 props/functions in the components where it isn't needed.

I tried using useContext Hook but I guess it doesn't work with callback functions in the value parameter.

App.js


const App = () => {
  const [ notes, setNotes ] = useState([]);
  const [ category, setCategory ] = useState(['Notes']);
  const [ searchText, setSearchText ] = useState('');
  const [ alert, setAlert ] = useState({
    show:false,
    msg:'',
    type:''
  });

  const [isEditing, setIsEditing] = useState(false);
  const [editId, setEditId] = useState(null);
  
  useEffect(()=>{
    keepTheme();
  })

  // retreive saved notes
  useEffect(()=>{
    const savedNotes = JSON.parse(localStorage.getItem('react-notes-data'));

    if (savedNotes) {
      setNotes(savedNotes)
    }
    
  }, []);

  // save notes to local storage
  useEffect(() => {
    localStorage.setItem('react-notes-data', JSON.stringify(notes))
    setNotesCopy([...notes]);
  }, [notes]);

  // save button will add new note
  const addNote = (text) => {
    const date = new Date();
    const newNote = {
      id: nanoid(),
      text: text,
      date: date.toLocaleDateString(),
      category: category,
    }
    const newNotes = [...notes, newNote]
    setNotes(newNotes);
  }

  const deleteNote = (id) => {
    showAlert(true, 'Note deleted', 'warning');
    const newNotes = notes.filter(note => note.id !== id);
    setNotes(newNotes);
  }

  // hardcoded values for categories
  const allCategories = ['Notes', 'Misc', 'Todo', 'Lecture Notes', 'Recipe'];
  
  // copy notes for filtering through
  const [notesCopy, setNotesCopy] = useState([...notes]);
  const handleSidebar = (category) => {
    setNotesCopy(category==='Notes'?[...notes]:
    notes.filter(note=>note.category===category));
  }
  
  // function to call alert
  const showAlert = (show=false, msg='', type='') => {
    setAlert({show, msg, type});
  }
  
  return (
    <div>
      <div className="container">

        <Sidebar 
          allCategories={allCategories}
          handleSidebar={handleSidebar}
          notesCopy={notesCopy}
          key={notes.id}
          />

        <Header notes={notes} alert={alert} removeAlert={showAlert} />

        <Search handleSearchNote={setSearchText} />

        <NotesList 
          notesCopy={notesCopy.filter(note=>
            note.text.toLowerCase().includes(searchText) ||
            note.category.toString().toLowerCase().includes(searchText) 
          )} 
          handleAddNote={addNote} 
          deleteNote={deleteNote} 
          category={category}
          setCategory={setCategory}
          allCategories={allCategories}
          showAlert={showAlert}
          notes={notes}
          setNotes={setNotes}
          editId={editId}
          setEditId={setEditId}
          isEditing={isEditing}
          setIsEditing={setIsEditing}
        />
        
      </div>
    </div>
  )
}

NotesList.js


const NotesList = (
  { notesCopy, handleAddNote, deleteNote, category, setCategory, showHideClassName, allCategories, showAlert, isEditing, setIsEditing, notes, setNotes, editId, setEditId }
  ) => {

  const [ noteText, setNoteText ] = useState('');
  const textareaRef = useRef();

  // function to set edit notes
  const editItem = (id) => {
    const specificItem = notes.find(note=>note.id === id);
    setNoteText(specificItem.text);
    setIsEditing(true);
    setEditId(id);
    textareaRef.current.focus();
  }
  
  return (
    <div key={allCategories} className="notes-list">
      {notesCopy.map(note => {
        return (
          <Note 
            key={note.id}
            {...note}
            deleteNote={deleteNote}
            category={note.category}
            isEditing={isEditing}
            editId={editId}
            editItem={editItem}
          />)
      })}
      <AddNote 
        handleAddNote={handleAddNote} 
        category={category} 
        setCategory={setCategory} 
        showHideClassName={showHideClassName} 
        allCategories={allCategories}
        showAlert={showAlert}
        isEditing={isEditing}
        setIsEditing={setIsEditing}
        notes={notes}
        setNotes={setNotes}
        editId={editId}
        setEditId={setEditId}
        noteText={noteText}
        setNoteText={setNoteText}
        textareaRef={textareaRef}
      />
    </div>
  )
}

AddNote.js


const AddNote = ({ notes, setNotes, handleAddNote, category, setCategory, showHideClassName, allCategories, showAlert, isEditing, setIsEditing, editId, setEditId, noteText, setNoteText, textareaRef }) => {
  const [ show, setShow ] = useState(false);
  const [ modalText, setModalText ] = useState('');

  const charCount = 200;
  const handleChange = (event) => {
    if (charCount - event.target.value.length >= 0) {
      setNoteText(event.target.value);
    }
  }
  
  const handleSaveClick = () => {
    if (noteText.trim().length === 0) {
      setModalText('Text cannot be blank!');
      setShow(true);
    }
    if (category === '') {
      setModalText('Please select a label');
      setShow(true);
    }
    if (noteText.trim().length > 0 && category!=='') {
      showAlert(true, 'Note added', 'success');
      handleAddNote(noteText);
      setNoteText('');
      setShow(false);
    }

    if (noteText.trim().length > 0 && category!=='' && isEditing) {
      setNotes(notes.map(note=>{
        if (note.id === editId) {
          return ({...note, text:noteText, category:category})
        }
        return note
      }));
      setEditId(null);
      setIsEditing(false);
      showAlert(true, 'Note Changed', 'success');
    }

  }

  const handleCategory = ( event ) => {
    let { value } = event.target;
    setCategory(value);
  }  
  showHideClassName = show ? "modal display-block" : "modal display-none";

  return (
    <div className="note new">
      <textarea 
        cols="10" 
        rows="8" 
        className='placeholder-dark' 
        placeholder="Type to add a note.."
        onChange={handleChange} 
        value={noteText}
        autoFocus
        ref={textareaRef}
        >
      </textarea>
      <div className="note-footer">
        <small 
          className='remaining' 
          style={{color:(charCount - noteText.length == 0) && '#c60000'}}>
        {charCount - noteText.length} remaining</small>

      <div className='select'>
        <select 
            name={category} 
            className="select" 
            onChange={(e)=>handleCategory(e)}
            required
            title='Select a label for your note'
            defaultValue="Notes"
          >
          <option value="Notes" disabled selected>Categories</option>
          {allCategories.map(item => {
            return <option key={item} value={item}>{item}</option>
          })}
        </select>
      </div>
        <button className='save' onClick={handleSaveClick} title='Save note'>
        <h4>{isEditing ? 'Edit':'Save'}</h4>
        </button>
      </div>


      {/* Modal */}
      <main>
        <div className={showHideClassName}>
          <section className="modal-main">
            <p className='modal-text'>{modalText}</p>
            <button type="button" className='modal-close-btn' 
              onClick={()=>setShow(false)}><p>Close</p>
            </button>
          </section>
        </div>
      </main>

    </div>
  )
}

I want the functions passed from App.js to NotesList.js to be in AddNote.js without them being passed in NotesList.js basically minimizing the props destructuring in NotesList.js


Context API does work with function. What you need to do is pass your function to Provider inside value :

<MyContext.Provider value={{notes: notesData, handler: myFunction}} >

For example:

// notes-context.js

import React, { useContext, createContext } from 'react';

const Context = createContext({});

export const NotesProvider = ({children}) => {
  
  const [notes, setNote] = useState([]);

  const addNote = setNote(...); // your logic
  const removeNote = setNote(...); // your logic

  return (
    <Context.Provider value={{notes, addNote, removeNote}}>
      {children}
    </Context.Provider>
  )

}

export const useNotes = () => useContext(Context);

Add Provider to your App.js like so:

// App.js
import NoteProvider from './notes-context';

export default App = () => {
  return (
    <NoteProvider>
      <div>... Your App</div>
     </NoteProvider>
  )
}

Then call UseNote in your NoteList.js to use the function:

// NoteList.js

import {useNotes} from './note-context.js';

export const NoteList = () => {
  const {notes, addNotes, removeNotes} = useNotes();

  // do your stuff. You can now use functions addNotes and removeNotes without passing them down the props

}