Can't stop setInterval after page reload
Answered
Knopper gall posted this in #help-forum
Knopper gallOP
I'm sure I'm being a moron and doing something obviously wrong.
Basically I can start/stop a subprocess. The server passes the initial states (logs and running state) to the client before the page is served.
The button green (start) or red (stop) based on this initial state.
When you click start, a server action is called to start the Subprocess, but also, in an useEffect, a setInterval is called to update the logs every second
The issue is, if you remain on the page whilst start/stopping, it works as intended, but if you start, reload, then stop, the setInterval continues to run.
How can I fix this please?
Basically I can start/stop a subprocess. The server passes the initial states (logs and running state) to the client before the page is served.
The button green (start) or red (stop) based on this initial state.
When you click start, a server action is called to start the Subprocess, but also, in an useEffect, a setInterval is called to update the logs every second
The issue is, if you remain on the page whilst start/stopping, it works as intended, but if you start, reload, then stop, the setInterval continues to run.
How can I fix this please?
Answered by Knopper gall
Turns out you are supposed to return a function in useEffect to unmount the component, whoopsie
6 Replies
Knopper gallOP
components/Controls.tsx
"use client";
import { Textarea } from "@/components/ui/textarea";
import { useState, useEffect, useTransition, useRef } from "react";
import { getLogOutput } from "@/actions/ProcessManager";
import { ReloadIcon, PlayIcon, StopIcon } from "@radix-ui/react-icons";
import { Button } from "@/components/ui/button";
import { handleStartStopProcess } from "@/actions/ProcessManager";
export default function Controls({ initialLogs, initialIsRunning }: { initialLogs: string, initialIsRunning: boolean}) {
const [logOutput, setLogOutput] = useState(initialLogs);
const [isRunning, setIsRunning] = useState(initialIsRunning);
const [isPending, startTransition] = useTransition();
const intervalRef = useRef<Timer | null>(null);
useEffect(() => {
if (isRunning) intervalRef.current = setInterval(() => getLogOutput().then(setLogOutput), 1000);
else clearInterval(intervalRef.current!);
}, [isRunning]);
return (
<>
<Textarea className="grow resize-none !cursor-auto mb-4 bg-slate-400 dark:bg-slate-800" value={logOutput} disabled />
<Button
variant={isRunning ? "destructive" : "success"}
onClick={() => startTransition(async () => {
handleStartStopProcess(isRunning).then(setIsRunning);
})}
disabled={isPending}
>
<RenderButtonContent isRunning={isRunning} isPending={isPending} />
</Button>
</>
);
};
function RenderButtonContent({isRunning, isPending }: { isRunning: boolean, isPending: boolean}) {
if (isPending) return <> <ReloadIcon className="mr-2 animate-spin"/> Please wait </>;
return isRunning ? <><StopIcon className="mr-2"/> Stop </> : <><PlayIcon className="mr-2"/> Start </>;
}page.tsx
import Controls from "@/components/Controls";
import { getLogOutput, isProcessRunning } from "@/actions/ProcessManager";
export default async function Home() {
const logOutput = await getLogOutput();
const isRunning = await isProcessRunning();
return (
<main className="h-screen flex justify-center items-center">
<div className="flex flex-col w-9/12 h-5/6">
<Controls initialLogs={logOutput} initialIsRunning={isRunning} />
</div>
</main>
);
}actions/ProcessManager.ts
"use server";
import { Subprocess } from "bun";
export const handleStartStopProcess = (isRunning: boolean) => isRunning ? stopProcess() : startProcess();
export const isProcessRunning = async () => !!appProcess;
let appProcess: Subprocess | null = null;
let logStream: ReadableStream<Uint8Array> | null = null;
export async function getLogOutput(): Promise<string> {
if (!appProcess || !logStream) return `[${new Date().toLocaleTimeString()}] Process is not running`;
return "To Be Added";
}
async function startProcess() {
if (appProcess) return true;
appProcess = Bun.spawn({
cmd: ["bun", "run", "."],
cwd: "bot",
});
return true;
}
async function stopProcess() {
if (!appProcess) return false;
appProcess.kill("SIGINT");
await appProcess.exited;
appProcess = null;
return false;
}Knopper gallOP
Live app
You can see in the network tab of dev tools when the interval is running
Knopper gallOP
Turns out you are supposed to return a function in useEffect to unmount the component, whoopsie
Answer