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?