Skip to content

Why can’t I use dot notation on React State?

I’m creating a flashcard app and I’m currently trying to set the front side of the flashcard to some text from an API.

My state:

const [deckWithCards, setDeckWithCards] = useState([]);

deckWithCards is a deck of flashcards and it looks like:

{name: 'Test Name', description: 'Test Description', id: 3, cards: Array(4)};

When I do deckWithCards.cards I get:

[{id: 1, front: 'Front of card', back: 'Back of Card', deckId: 1}]

If I was to have 4 cards in a deck, I’ll get an array with 4 of these objects with the corresponding data.

I need access to all of this information however, when I try to do deckWithCards.cards.front, I get “Cannot read property ‘front’ of undefined.”

I also tried looping through the cards array like:

let arr = [];
let allCards = deckWithCards.cards;
for (let i = 0; i < allCards.length; i++) {
   arr.push(allCards.front);
}

This gave me: “Cannot read property ‘length’ of undefined.”

How do I gain access to the items in this cards array?

Helper functions:

export async function readDeck(deckId, signal) {
  const url = `${API_BASE_URL}/decks/${deckId}?_embed=cards`;
  return await fetchJson(url, { signal });
}

export async function listCards(deckId, signal) {
  const url = `${API_BASE_URL}/cards?deckId=${deckId}`;
  return await fetchJson(url, { signal });
}

How I set my State:

useEffect(() => {
    const abortController = new AbortController();

      readDeck(deckId, abortController.signal)
      .then(setDeckWithCards)
      .catch(setError)

      listCards(deckId, abortController.signal)
        .then(setCards)
        .catch(error)

      return () => abortController.abort();
  }, []);

Answer

There is a moment in time while your useEffect and your fetch are still running before you set the cards. During that time, the value of deckWithCards is going to be the initial value that you provided in your useState. Your component has to be built in a way where it can run properly and render properly with that initial value. If the eventual value of the resolved deck is an object, then it makes no sense that your initial value is an empty array.

const [deckWithCards, setDeckWithCards] = useState([]);

I recommend that you set the initial state to null or undefined. Before you access any properties on deckWithCards, you have to check that it has been set to an actual value.

const [deckWithCards, setDeckWithCards] = useState(null);
const allCards = deckWithCards ? deckWithCards.cards : [];

Here we check if deckWithCards is truthy (not null). If we have a deck, then we access the cards from the deck. If it’s still null, we use an empty array. Either way, allCards will always be an array that you can map, loop through, etc.

const fronts = allCards.map( card => card.front );
return (
  <ul>
    {allCards.map( (card) => (
       <div className="card" key={card.id}>
          {card.front}
       </div>
    ))}
  </ul>
)