E Tech.

How to use setInterval, promise, and useEffect in Wails

Introduction

Now I'm working on a desktop app that displays interview questions a user has prepared. The view shows questions randomly every few seconds, reading from a file. Since I use Wails and write a method to read/edit files in backend with Go, it needs to manage setInterval, Promise, and useEffect together in the frontend React. For me, juggling these in the right order was a bit of a puzzle! I finally found one approach that works; maybe there's an even better solution out there, but I thought I'd share my current setup as a quick memo.

Challange

So, what I wanted to do was:
・Read data from a file in backend
・Pass it to frontend
・Keep the data as state
・Display each item from that data every few seconds

I think there were two points to consider.

1.useEffect + Promise

In Wails, frontend uses JavaScript and backend uses Go. When frontend calls a bound Go method the return value is a Promise.
From Wails documentation for example :

// ...
import { Greet } from "../wailsjs/go/main/App";

function doGreeting(name) {
  Greet(name).then((result) => {
    // Do something with result
  });
}

2.useEffect + setInterval

To show different questions every few seconds, I needed to update the view at regular intervals. You can use setInterval in useEffect, it requires to clean the interval.
From the article "How to use setInterval() method inside React components ?", an example looks like this:

import React, { useState, useEffect } from "react";

const App = () => {
    const [count, setCount] = useState(0);

    useEffect(() => {
        //Implementing the setInterval method
        const interval = setInterval(() => {
            setCount(count + 1);
        }, 1000);

        //Clearing the interval
        return () => clearInterval(interval);
    }, [count]);

    return (
            <h1>{count}</h1>
    );
};

export default App;

What makes me confusing

My question was how to use Promise and setInterval within a single useEffect. Where should I clean the interval? What should I define in dependency array?

Solution

The solution is a little simple.
My code is here:

import { useEffect, useState } from 'react';
import { main } from "../../wailsjs/go/models"
import { ReadQuestionFile } from '../../wailsjs/go/main/App';

function DisplayQuestion() {
    const [interviewQuestion, setInterviewQuestion] = useState<string[]>([])
    const [interviewQuestionIndex, setInterviewQuestionIndex] = useState<number>(0)

    useEffect(() => {
        ReadQuestionFile("company.yaml").then((result) => {
            const data = getInterviewQuestions(result)
            setInterviewQuestion(data)
        })
    }, [])

    useEffect(() => {
        let intervalId: number
        if (interviewQuestion.length != 0) {
            intervalId = setInterval(() => {
                setInterviewQuestionIndex(interviewQuestionIndex + 1)
            }, 3 * 1000)
        }
        return () => clearInterval(intervalId)

    }, [interviewQuestion, interviewQuestionIndex])

    function getInterviewQuestions(q: main.Questions): string[] {
        const result: string[] = []

        result.push(...(q.Stages["Early"] as string[]))
        result.push(...(q.Stages["Middle"].sort((a, b) => 0.5 - Math.random()) as string[]))
        result.push(...(q.Stages["Late"].sort((a, b) => 0.5 - Math.random()) as string[]))
        return result
    }

    return (
        <div id="DisplayQuestion">
            <div>
                Question from interviewe is : {interviewQuestion[interviewQuestionIndex]}
            </div>
        </div>
    )
}

export default DisplayQuestion

I use two useEffect here. The first useEffect only runs on the initial mount. It retrieves data from the backend and save it to state. The second useEffect updates the displayed question every 3 seconds. In my code, the variables interviewQuestion and interviewQuestionIndex are "reactive value", this second useEffect responds whenever these two variables change. From what I understand, interviewQuestion only updates once on the initial mount, triggering the second useEffect to start running thereafter.

Conclusion

According to the below reference, using multiple useEffect isn't bad idea. What I chose might be one of the solutions.

Reference :
How do you use multiple useEffect in a component?
Is it a bad practice to use multiple useEffect in a single component?