'Why does useEffect React Hook not work properly with dependency?

I'm working at a React Web App with Fullcalendar. I think one does not need to know Fullcalendar in order to answer this question. It's rather a matter of React understanding.

Long story short, Fullcalendar has an api that refetches any events from a given source. In my case, my source is state.events, iniated with useState. I only want to refetch, when my state changes. All good so far. However, at the first initial render, Fullcalendar is not able to fetch anything.

const OverviewPage: React.FunctionComponent<IOverviewPageProps> = (props) => {
    const calendarRef = useRef<any>();
    const [sourceAPI, setSourceAPI] = useState<any>(undefined);
    const [state, setState] = useState<State>({
        externalEvents: [],
        events: []
    });
   
useEffect(() => {
         if (calendarRef.current !== undefined) {
            let calendarApi = calendarRef.current.getApi();

            if (sourceAPI === undefined) {
                const source = calendarApi.addEventSource(state.events);
                source.refetch()
                setSourceAPI(source);
            } else {
                sourceAPI.refetch();
            }
        }
    }, [state])
return (<div>
           <FullCalendar
               ref={calendarRef}
               //other props
           />
        </div>)
;

Now, as you can see, as long as I don't have the reference of the calendar, I can't iniate the calendarApi. And as long as I don't have that api, I can't refetch. I do have to mention, when I take away the useEffect dependency (state), I no longer face an issue with fetching the events (even on the initial render). However, that causes other issues in my code that I wasn't able to fix, so I rather prefer to fix it with useEffect.

Update

It was requested to explain more in depth, what is happening in the code. Fullcalendar needs a source of events (calendar tasks). To provide this source, one has to iniate it before render, which I'm doing here in useEffect

Checking, if the reference to the element Fullcalendar already exists to prevent a render error

if (calendarRef.current !== undefined) {//...}

Checking, if a source was already added. If no, iniate a source by using Fullcalendar's api. .addEventSource adds a source that contains an array of objects (events) - in this case state.events - and returns a Fullcalendar Source so that I can interact with it later (e.g. refetch). .refetch() simply takes the content of the source and (re-)renders them on the calendar (so in other words the content of state.events). I then store this Fullcalendar Source in a React State so that I can interact with the same source later again, without having to reiniate it again.

 if (sourceAPI === undefined) {
                const source = calendarApi.addEventSource(state.events);
                source.refetch()
                setSourceAPI(source);
            }

If a Fullcalendar Source is already existing, apply the refetch call again.

else {
        sourceAPI.refetch();
}

There's really not more to say about Fullcalendar. I was suspecting the issue in React State management and that I'm doing something wrong there (with useEffect).



Solution 1:[1]

After trying out multiple different approaches, I finally managed that Fullcalendar also renders events on the first initial render. This might be helpful for people in the future, facing the same issue with React and Fullcalendar

const OverviewPage: React.FunctionComponent<IOverviewPageProps> = (props) => {
    const calendarRef = useRef<any>();
    const [sourceAPI, setSourceAPI] = useState<any>(undefined);
    const [state, setState] = useState<State>({
        externalEvents: [],
        events: []
    });
useEffect(() => {
        if (sourceAPI === undefined && calendarRef.current !== undefined) {
            const calendarApi = calendarRef.current.getApi();
            const source = calendarApi.addEventSource(state.events);
            setSourceAPI(source);
        }
    });
   
useEffect(() => {
        if (sourceAPI !== undefined) {
            sourceAPI.refetch();
        }
    }, [state]);

return (<div>
           <FullCalendar
               ref={calendarRef}
               //other props
           />
        </div>)
;

Pay attention that the first useEffect is triggered every time while the second one is only triggered when there was a change in regards of its dependency. This way, the source is initiated for sure, which it appeared to be the main problem with the privious code (calendarRef.current was always undefined on initial start/render)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Scorpia