Home > Mobile >  How to avoid prop drilling ? / How to use useContext?
How to avoid prop drilling ? / How to use useContext?

Time:01-16

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

CodePudding user response:

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

}

CodePudding user response:

https://www.w3schools.com/react/react_usecontext.asp there is a good explanation in here.

And also you should consider using Context API or Redux.

  •  Tags:  
  • Related