Home > Net >  A better way to render this function component?
A better way to render this function component?

Time:11-03

The goal of the below code is to pull data from a firebase realtime database into an array called popularMovies. (The amount of data in the realtime database is 1000.) Then a random index is picked from that array and the movie data at that index is displayed on the page. When the user clicks the liked button, that movie data is stored in a firestore document under the user's id, and another random index is chosen to display a new movie.

The below code mostly works, but I've noticed that the console.log("run!") executes 1000 times (the size of the data being read) both when the page loads and when the liked button is clicked. I was hoping to understand why that is the case given that it is outside the forEach loop that reads the data.

Additionally, when the button is clicked once, all the data updates but the display doesn't reflect the changes. However, clicking the button again does update the data again along with the display (as I would expect to have happened the first time). And this continues with the button only updating the display after every other push. I would also like to know what might be causing that bug.

import React, { useState, useEffect } from 'react';
import './App.css';
import firebase from 'firebase/compat/app';
import 'firebase/database';
import 'firebase/firestore';

function RateMovies() {

    const popularMoviesRef = firebase.database().ref("/popular_movies");
    const [popularMovies, setPopularMovies] = React.useState([]);
    var [render, setRender] = React.useState(1);

    console.log("run!");

    useEffect(() => {
        
        popularMoviesRef.once("value", function(snapshot){

            snapshot.forEach(function(childSnapshot){
                var key = childSnapshot.key;
                var data = childSnapshot.val();

                setPopularMovies(currMovies => [...currMovies, { 
                    key: key, 
                    movieTitle: data.movie_title, 
                    moviePoster: data.movie_poster, 
                    movieYear: data.movie_year,
                    movieGenre: data.movie_genre, 
                    movieRating: data.imdb_rating
                }])
            });
        });
    }, [render]);

    var index = Math.floor(Math.random() * (1000   1));
    var movie = popularMovies[index];

    const auth = firebase.auth();
    var db = firebase.firestore();
    var user_id = auth.currentUser.uid;

    function likedMovie() {

        db.collection('users').doc(user_id).update({
            "Rated.liked": firebase.firestore.FieldValue.arrayUnion({
                key: movie.key, 
                movieTitle: movie.movieTitle, 
                moviePoster: movie.moviePoster, 
                movieYear: movie.movieYear,
                movieGenre: movie.movieGenre, 
                movieRating: movie.movieRating,
             }),
        })
        .then(function(){
            console.log("Successfully updated!");
            setRender(render  );
        });

        index = Math.floor(Math.random() * (1000   1));
        movie = popularMovies[index];
    }


    return (
        <div>
            <p>Rate Movies Here</p>
            <div> {movie?.movieTitle} </div>
            <div> {movie?.movieYear} </div>
            <br/>
            <button onClick={likedMovie}> Like</button>
            <button> Disike</button>
            <br/>
            <br/>
            <img class="rate-image" src = {`${movie?.moviePoster}`} />
            
        </div>
    )
  
  } 

  export default RateMovies;

CodePudding user response:

The reason you see console.log() 1000 times is because your forEach() loop updates a state variable (popularMovies) defined in RateMovies(). Every time state changes, React will re-render your component.

Instead of adding a new item one at a time, create a new array in your forEach loop. This will only modify state a single time, meaning the component should only re-render once.

Try something like this:

useEffect(() => {
        
        popularMoviesRef.once("value").then(function(snapshot){
            
            var newArray = []
    
            snapshot.forEach(function(childSnapshot){
                var key = childSnapshot.key;
                var data = childSnapshot.val();

                newArray.push({ 
                    key: key, 
                    movieTitle: data.movie_title, 
                    moviePoster: data.movie_poster, 
                    movieYear: data.movie_year,
                    movieGenre: data.movie_genre, 
                    movieRating: data.imdb_rating
                })
            });

            setPopularMovies([...popularMovies, ...newArray])

        }));
    }, [render]);

As far as your button not re-rendering the data you want, I'm not 100% sure but it may have to do with the way JavaScript handles promises. The popularMoviesRef.once call in your useEffect may be happening at the same time as your db.collection('users').doc(user_id).update. You can test this by adding a console.log() to your useEffect function to try to see if it logs before "Successfully Updated!".

See here: What is the order of execution in JavaScript promises?

  • Related