0

I am creating a flashcard app in React using hooks and having trouble deleting a deck of flashcards. I am able to render Bootstrap cards on the page with the flashcards name, description, card count, and the buttons as desired. However, I am unable to delete a deck of cards as I'm being told setFlashcards is not a function. Here is my code:

App.js

function Layout() {
  const [flashcards, setFlashcards] = useState([])

  useEffect(() => {
    axios
    .get('http://localhost:5000/decks?_embed=cards')
    .then(res => {
      setFlashcards(res.data.map((questionItem, index) => {
        return {
          id: `${index}-${Date.now()}`,
          name: questionItem.name,
          description: questionItem.description,
          cards: questionItem.cards.length,
        }
      }))
    })
  }, [])

  return (
    <div>
      <Header />
      <ShowAllDecks flashcards={flashcards} setFlashcards={setFlashcards} />
      <NotFound />
    </div>
  )
}

ShowAllDecks.js

function ShowAllDecks({ flashcards, setFlashcards }) {

    return (
        <div className='container'>
            <button>Create New</button>
            {flashcards.map((flashcard) => {
                return <Deck flashcards={flashcards} flashcard={flashcard} key={flashcard.id} />
            })}
        </div>
    )
}

Deck.js

function Deck({ flashcard, flashcards, setFlashcards }) {
    const deleteHandler = () => {
        setFlashcards(flashcards.filter(el => el.id !== flashcard.id))
    }


    return (
        <div className='container'>
            <div className='card'>
                <div className='card-body'>
                    <h3 className='card-title'>{flashcard.name}</h3>
                    <p className='card-text'>{flashcard.description}</p>
                    <p className='card-text'>{flashcard.cards} cards</p>
                    <button>View</button>
                    <button>Study</button>
                    <button onClick={deleteHandler}>Delete</button>
                </div>
            </div>
        </div>
    )
}

Example of a deck with one card:

[
  {
    'id': 1,
    'name': 'A Deck Name'
    'description': 'A Deck Description',
    'cards': [
      {
        'id': 1,
        'front': 'Front of card',
        'back': 'Back of card',
        'deckId': 1
      }
    ]
  }
]
5
  • 1
    You forgot to pass setFlashcards to Deck Commented Feb 7, 2021 at 0:41
  • Instead of passing the state updater function all the way through to children and making it a child's responsibility to correctly maintain the state invariant from all the way back in Layout where the state is, you should keep the delete handler in the same component with the state and pass a reference to the delete handler on to children. The delete handler should consume an "id"/index of the data element to remove from state. The delete handler should also use a functional update so you don't need to also pass the state object around either. Commented Feb 7, 2021 at 0:46
  • The onClick handler of the button should be <button onClick={() => deleteHandler}>Delete</button> Commented Feb 7, 2021 at 0:48
  • @Joe That won't work, still need to invoke deleteHandler. OP's code is fine there though. Commented Feb 7, 2021 at 0:49
  • 1
    deleteHandler is inside the deck itself, and the deck itself has access to the flashcards array, the single flashcard being rendered, and the setFlashcards hook (although I've never passed this through props so no idea if there would be any problems with it). If it's not written in an arrow function wouldn't it be called on render? But I agree the delete function should be in the App.js and then take an id, then passed down to the component. Then can pass the id through to that as you say, and the setFlashcards can run in App.js, don't have to pass so much down. Commented Feb 7, 2021 at 0:57

1 Answer 1

1

I'm assuming you're running call to get the flashcards in App.js because you're going to want to pass it to other components? Might be best to use Context too if you're going to drill the props down to other components a lot. Otherwise if it's only going to be for showing All decks then you can run the fetch inside the AllDecks component. Anyway I've changed your code below keeping it in App.js:

App.js

function Layout() {
  const [flashcards, setFlashcards] = useState([])

  const deleteHandler = (id) => {
     setFlashcards(flashcards.filter(deck => deck.id !== id));
  }

  useEffect(() => {
    axios
    .get('http://localhost:5000/decks?_embed=cards')
    .then(res => {
      setFlashcards(res.data.map((questionItem, index) => {
        return {
          id: `${index}-${Date.now()}`,
          name: questionItem.name,
          description: questionItem.description,
          cards: questionItem.cards.length,
        }
      }))
    })
  }, [])

  return (
    <div>
      <Header />
      <ShowAllDecks flashcards={flashcards} deleteHandler={deleteHandler} />
      <NotFound />
    </div>
  )
}

ShowAllDecks.js

function ShowAllDecks({ flashcards, deleteHandler }) {

    return (
        <div className='container'>
            <button>Create New</button>
            {flashcards.map((flashcard) => {
                return <Deck flashcard={flashcard} key={flashcard.id} deleteHandler={deleteHandler} />
            })}
        </div>
    )
}

Deck.js

function Deck({ flashcard, deleteHandler }) {


    return (
        <div className='container'>
            <div className='card'>
                <div className='card-body'>
                    <h3 className='card-title'>{flashcard.name}</h3>
                    <p className='card-text'>{flashcard.description}</p>
                    <p className='card-text'>{flashcard.cards} cards</p>
                    <button>View</button>
                    <button>Study</button>
                    {/* 
                     ** this will now run the deleteHandler in the App component and pass 
                     ** the id of the current deck to it. It will then run the state hook
                     ** and set the new flashcards without this deck 
                     */}
                    <button onClick={() => deleteHandler(flashcard.id)}>Delete</button>
                </div>
            </div>
        </div>
    )
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.