0

My useEffect is running multiple times per load even when the useEffect dependency isn't being updated. See console.log("filtering events"). Every time the page is loaded the useEffect runs three times (my best guess is because the components are being rendered three times?).

What I am trying to do is every time the filters state variable is updated I want the filteredEvents variable to be updated from the filters applied to all of the events and then the calendar component to be re-rendered with the new events.

import React, { useState, useEffect, useCallback } from 'react'
import { Calendar as BigCalendar, momentLocalizer } from 'react-big-calendar';
import moment from 'moment';
import loadingIMG from "../../../assets/images/courses/signup/minionsLoading.gif"
import "react-big-calendar/lib/css/react-big-calendar.css"
import {
    Accordion,
    AccordionItem,
    AccordionItemHeading,
    AccordionItemPanel,
    AccordionItemButton
} from 'react-accessible-accordion'
import { navigate, Link } from 'gatsby';

moment.locale('en-US')
const localizer = momentLocalizer(moment)

const CourseSignup = ({className}) => {
    const [loading, isLoading] = useState(true)

    const [allEvents, setAllEvents] = useState({
        count: undefined,
        events: undefined
    })

    useEffect(async () => {
        console.log("fetching events")
        await fetch("http://127.0.0.1:5000/timeslots", { 
            method: 'GET',
            headers: {
                'X-API-Key': '*F-JaNdRgUkXp2s5v8x/A?D(G+KbPeSh',
            }
        })
        .then(response => response.json())
        .then((results) => {
            let temp = []
            // Get all events
            for (var i = 0; i < results.events.length; i++) {
                // TODO: Check if class is virtual or in-person
                // TODO: Check if class is group or 1 on 1
                // TODO: get teacher name
                // TODO: get class name

                // Get all neccesary information and push to an array
                temp.push({
                    title: String(results.events[i].summary),
                    info: {
                        format: undefined,
                        type: undefined,
                        name: undefined,
                        teacher: undefined
                    },
                    description: String(results.events[i].description) || undefined,
                    start: new Date(results.events[i].start.date || results.events[i].start.dateTime),
                    end: new Date(results.events[i].end.date || results.events[i].end.dateTime),
                    url: String(results.events[i].htmlLink),
                    id: String(results.events[i].id),
                    est_locale_timecodes: {
                        start: results.events[i].start.date || results.events[i].start.dateTime,
                        end: results.events[i].end.date || results.events[i].end.dateTime
                    }
                })
            }
            // update state variable with events
            setAllEvents({
                'count': temp.length,
                'events': temp
            })
        })
        .catch(() => {
            // incase of an error replace all events
            setAllEvents({
                'count': undefined,
                'events': undefined
            })
        })
        .then(() => isLoading(false))
    }, [])


    const UserInterface = () => {
        const [filters, updateFilters] = useState({
            classTeacher: 'undefined', // name of the teacher (default: undefined)
            classType: undefined, // 'group', '1 on 1'
            classFormat: undefined, // 'virtual', 'in-person'
            class: className // name of class (default: undefined)
        })
        const [filteredEvents, setFilteredEvents] = useState({
            count: undefined,
            events: undefined
        })

        useEffect(() => {
            console.log("filtering events")
            if(!Object.values(filters).every(filterValue => filterValue == undefined)) {
                // let filtered = Array(allEvents.events).filter(event => {
                //     if(event.info.format == undefined || String(event.info.format).includes(filters.classFormat == undefined ? "" : filters.classFormat)) return true
                //     if(event.info.type == undefined || String(event.info.type).includes(filters.classType == undefined ? "" : filters.classType)) return true
                //     if(event.info.name == undefined || String(event.info.name).includes(filters.class == undefined ? "" : filters.class)) return true
                //     if(event.info.teacher == undefined || String(event.info.teacher).includes(filters.classTeacher == undefined ? "" : filters.classTeacher)) return true
                //     return false
                // })

                setFilteredEvents(allEvents)
            } else {
                setFilteredEvents(allEvents)
            }
        }, [filters])

        const FilterOptions = () => {
            return null
        }

        const Calendar = () => {
            const getDate = () => {
                var d = new Date()
                return (`${d.toLocaleString('default', { month: 'long' })}, ${d.getFullYear()}`)
            }

            return (
                <div id="calendar-content">
                    <div className="section-title" style={{"marginBottom":"10px"}}>
                        <h3 style={{fontFamily: "inherit"}}>{getDate()}</h3>
                    </div>
                    <BigCalendar 
                        localizer={localizer}
                        events={filteredEvents.events} 
                        startAccessor="start" 
                        endAccessor="end" 
                        style={{ height: 800 }} 
                        views={{month: true}} 
                        popup={true}
                        toolbar={false}
                    />
                </div>
            )
        }

        const SignupForm = () => {
            return null
        }

        return (
            <div>
                <FilterOptions/>
                <div>
                    {loading
                        ? null
                        : <Calendar/>
                    }
                </div>
                <SignupForm/>
            </div>
        )
    }

    return (
        <UserInterface/>
    )

}

export default CourseSignup
halfer
  • 19,824
  • 17
  • 99
  • 186

1 Answers1

0

Are you wrapping your App Component with StrictMode Component inside index.js, if yes? then remove it and you will get the expected results and also the component is only rendered-once

And one more thing you're doing wrong which is not considered a good practice is, not to declare the callback function as async like this =>

useEffect(async() => {}, [])

Because if you will do that, then if you want to return a cleanup function from inside useEffect the next time it runs, you won't be to do so

For example, let's say you have a search engine which is tied to the backend through useEffect, now on every input change the useEffect will be triggered

useEffect(() => {} , [onEveryInputChange])

Now every time useEffect is gonna run a timeout will be registered and a request will be sent to the backend once the time expires

useEffect(() => {
    const myTimeout = setTimeout(() => fetch('someUrl').then().catch(), [2000])
}, [onEveryInputChange])

Now you don't want to send req for every input, and you only want to send a req once you are done typing or once you have taken a pause of 2 secs after writing down a few words, so in that case, we use cleanup function

useEffect(() => {
      const myTimeout = setTimeout(() => fetch('someUrl').then().catch(), [2000])
    
    return () => clearTimeout(myTimeout) // This is a cleanup function
}, [onEveryInputChange])

So the next time you enter something within 2 sec the cleanup will run first and will prevent the req from getting executed

Mridul Gupta
  • 807
  • 1
  • 6
  • 8