0

I am trying to setEventsList state to have the eventList but I always get an infinite loop. I have successfully gotten the data from firebase as objects and I also want to change the object into an array so that the map function does not throw an error.

import React, { useEffect, useState } from 'react';
import { realDB } from '../../firebase/firebase'
import Card from '../events/events'

function CardList(props) {
  const [eventsList, setEventsList] = useState([]);
  useEffect(() => {
    const eventsRef = realDB.ref('/Events').limitToFirst(5);
    eventsRef.on('value', (snapshot) => {
      var events = snapshot.val();
      let eventList = []
      for (let id in events) {
        eventList.push({
          EventName: events[id].EventName,
          EventEntryFee: events[id].EventEntryFee,
          Eventsport: events[id].Eventsport,
          eventCurrentParticipants: events[id].eventCurrentParticipants,
          EventMaximumParticipants: events[id].EventMaximumParticipants,
          EventTotalPrizes: events[id].EventTotalPrizes,
          EventDifficulty: events[id].EventDifficulty
        });
      }
      setEventsList(eventList);
      console.log(eventsList);
    });
  }, [])

  return (
    <div>
      {eventsList
        ? eventsList.map((event, index) => (
          <Card key={index} />
        )) : (
          <><p> No data</p></>
        )}
    </div>
  );
}

export default CardList;
// contents of '../../firebase/firebase'

import firebase from "firebase";
import 'firebase/auth'

// Initialize Firebase
const app = firebase.initializeApp({
  apiKey: "AIzaSyBxxxxx1wQA",
  authDomain: "fantasxxxxxxxaxxpp.x",
  databaseURL: "https:xxxxxe.x.com",
  projectId: "fxxxxxe",
  storageBucket: "fantxxxm",
  messagingSenderId: "xxxx",
  appId: "1:xxx",
  measurementId: "Gxx-xxxxx"
});

// firebase.analytics();
const db = app.firestore()
const realDB = app.database()
const auth = app.auth()
export { db, auth, realDB }
10
  • Does eventsRef need to unsubscribe from anything to remove the listener and end state updates? Commented Jun 13, 2021 at 6:05
  • I am still new to react. I just want to set the state of eventsList to the eventList array and map it to the card component. How can I do that without getting an infinite loop with the current code? Commented Jun 13, 2021 at 6:19
  • I dont think it should Commented Jun 13, 2021 at 6:20
  • 1
    Not really sure, the useEffect hook only runs once, so either many onValue events are occurring, or this CardList component is being repeatedly remounted so the effect is running each time. Do you know how often that event is emitted? You can easily test the remounting theory with useEffect(() => console.log('MOUNTED'), []); and if you see a bunch then you know it's remounting. Commented Jun 13, 2021 at 6:26
  • @DrewReese only once Commented Jun 13, 2021 at 6:43

1 Answer 1

1

In your current code, there are a number of bugs that could be contributing to the problem.

First, you don't unsubscribe the snapshot listener whenever the component is unmounted. If the parent component removes/readds your CardList component, this will cause problems such as updating state out of scope.

You shouldn't use for (let id in snapshot.val()), as the ordering from your database query will be discarded. Instead you should use the DataSnapshot#forEach method provided by the Firebase SDK.

I also advise taking care with the Firebase RTDB in regard to differing between a Reference and a Query as the DataSnapshot objects they provide must be consumed in different ways.

When working with arrays in React, you should take care to provide each child element inserted using map with a key property. In your current code, you make use of the array's index for this but you should use the entry's database key instead so React can efficiently handle the components shuffling around or getting deleted. In a similar vein, your "no data" entry should have a similar key.

You talked about how your map function was causing errors if it was set incorrectly, instead of using just eventsList, you should also handle the case where your data is still loading. You can either handle this using a type of Event[] | null or using a separate const [loading, setLoading] = useState(true).

Lastly, you don't have any error handling logic in your code. This could be handled using console logs, but you should set up a errorMsg state variable to display a message to the user when something has gone wrong.

So the following changes need to be made:

  • Properly unsubscribe the value listener
  • Use DataSnapshot#forEach
  • Rename eventsRef to eventsQuery
  • Rename snapshot to querySnapshot
  • Apply way of handling the loading state.
  • Apply error handling.
import React, { useEffect, useState } from 'react';
import { realDB } from '../../firebase/firebase'
import Card from '../events/events'

function CardList(props) {
  const [eventsList, setEventsList] = useState(null); // null -> loading
  const [errorMsg, setErrorMsg] = useState(null); // null -> no error

  useEffect(() => {
    const eventsQuery = realDB.ref('/Events').limitToFirst(5);

    const valueListener = eventsQuery.on(
      'value',
      (querySnapshot) => {
        const eventList = [];
        querySnapshot.forEach(eventSnapshot => {
          const eventData = eventSnapshot.val();
          eventList.push({
            _key: eventSnapshot.key;
            EventName: eventData.EventName,
            EventEntryFee: eventData.EventEntryFee,
            Eventsport: eventData.Eventsport,
            eventCurrentParticipants: eventData.eventCurrentParticipants,
            EventMaximumParticipants: eventData.EventMaximumParticipants,
            EventTotalPrizes: eventData.EventTotalPrizes,
            EventDifficulty: eventData.EventDifficulty
          });
        }
        console.log("refreshing eventsList", eventsList);
        setEventsList(eventList);
        setErrorMsg(null);
      },
      (error) => {
        setEventsList([]);
        setErrorMsg("Something went wrong.");
      }
    );

    return () => eventsQuery.off('value', valueListener);
  }, []);

  if (eventsList === null) {   // is loading?
    return (<div><key="loading"><p>loading...</p></></div>);
  }

  if (errorMsg !== null) {     // has error?
    return (<div><key="error"><p>{errorMsg}</p></></div>);
  }

  return (
    <div>
      { eventsList.length > 0
        ? eventsList.map((event) => (
          <Card key={event._key} />
        )) : (
          <key="nodata"><p>No data</p></>
        )
      }
    </div>
  );
}

export default CardList;

If the error persists, you'll need to look at the parent component and see if it is rerendering rapidly.

Side Note: eventCurrentParticipants should be renamed EventCurrentParticipants and Eventsport as EventSport to be consistent with the rest of your data. Although, using Event at the start of each property is also superfluous.

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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.