Multi-ID Status Tracker with RTK polling
β The issue is this line:
This is incorrect usage of RTK Query's lazy query trigger
function. You are passing a raw id
instead of the expected object β and true
as a second argument which isn't needed here.
β Fix it like this:
In useStatusPoller.js
, update this line:
import { useState, useEffect } from "react";
import { useLazyGetStatusQuery } from "../services/chunkApi";
const terminalStatuses = ["completed", "failed"];
export default function useStatusPoller(id, enabled = true) {
const [trigger] = useLazyGetStatusQuery();
const [status, setStatus] = useState("pending");
const [progress, setProgress] = useState(0);
useEffect(() => {
if (!enabled || !id) return;
let stopped = false;
async function poll() {
if (stopped) return;
try {
const res = await trigger({ id }).unwrap(); // β
FIXED
if (res) {
setStatus(res.status);
setProgress(res.progress);
if (!terminalStatuses.includes(res.status)) {
setTimeout(poll, 5000);
}
}
} catch (error) {
console.error(`Error polling for ${id}:`, error);
setTimeout(poll, 10000);
}
}
poll();
return () => {
stopped = true;
};
}, [id, enabled, trigger]);
return { status, progress };
}
import React, { useState } from "react";
import StatusDisplay from "./StatusDisplay";
import { useGetStatusListingQuery } from "../services/chunkApi";
export default function MultiStatusPoller() {
const { data: statusListing } = useGetStatusListingQuery();
return (
<div>
{statusListing?.map((item) => (
<StatusDisplay key={item?.id} id={item?.id} />
))}
</div>
);
}
import React, { useState } from "react";
import useStatusPoller from "./useStatusPoller";
export default function StatusDisplay({ id }) {
const [started, setStarted] = useState(false);
const { status, progress } = useStatusPoller(id, started);
const handleStart = () => {
setStarted(true);
};
return (
<div
style={{
marginBottom: "1rem",
border: "1px solid #ddd",
padding: "10px",
borderRadius: "6px",
}}
>
{!started ? (
<button onClick={handleStart}>Start</button>
) : (
<>
<h4>ID: {id}</h4>
<p>
Status: <strong>{status}</strong>
</p>
<progress value={progress} max="100" style={{ width: "100%" }} />
<p>{progress}%</p>
</>
)}
</div>
);
}
β Also, confirm your chunkApi definition:
Make sure your API slice has this:
And you're exporting it:
β Test and verify:
- When you click "Start" for an ID, it should now call
trigger({ id })
, get the status, and keep polling untilcompleted
orfailed
.
Let me know if you also want to:
- Add a Stop button,
- Show loading/error states,
- Or batch trigger polling for multiple IDs.
- Home: index.md
- Setup: setup.md
- Components: components.md
- API Mocking: mocking.md
- Polling Logic: polling.md
Updated Features
Start and Stop
import React, { useState } from "react";
import useStatusPoller from "./useStatusPoller";
export default function StatusDisplay({ id }) {
const [started, setStarted] = useState(false);
const { status, progress, loading, error, stop } = useStatusPoller(
id,
started
);
const handleStart = () => {
setStarted(true);
};
const handleStop = () => {
stop();
setStarted(false);
};
return (
<div
style={{
marginBottom: "1rem",
border: "1px solid #ddd",
padding: "10px",
borderRadius: "6px",
backgroundColor: "#f9f9f9",
}}
>
<h4>ID: {id}</h4>
{!started ? (
<button onClick={handleStart}>Start</button>
) : (
<>
<button onClick={handleStop} style={{ marginBottom: "10px" }}>
Stop
</button>
<p>
Status: <strong>{status}</strong>
</p>
<progress value={progress} max="100" style={{ width: "100%" }} />
<p>{progress}%</p>
{loading && <p style={{ color: "blue" }}>Polling...</p>}
{error && <p style={{ color: "red" }}>{error}</p>}
</>
)}
</div>
);
}
β Result:
- Start and Stop buttons to control polling
- Shows "Polling..." while loading
- Shows error messages if polling fails
-
Stops polling when:
- You click "Stop"
- The status reaches
completed
orfailed
Global: Start/Stop All
- Automatically restart polling after failure
- Add global polling controls (start all / stop all)
- Make a timeline or animation to show progress visually
full working setup that includes
- β Start/Stop polling per ID
- β Loading and error states
- β Optional: "Start All" / "Stop All" button (at bottom)
π§ 1.
useStatusPoller.js
(with polling + stop + error/loading)
import { useState, useEffect, useRef } from "react";
import { useLazyGetStatusQuery } from "../services/chunkApi";
const terminalStatuses = ["completed", "failed"];
export default function useStatusPoller(id, enabled = true) {
const [trigger] = useLazyGetStatusQuery();
const [status, setStatus] = useState("idle");
const [progress, setProgress] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const stopRef = useRef(false);
useEffect(() => {
if (!enabled || !id) return;
stopRef.current = false;
async function poll() {
if (stopRef.current) return;
try {
setLoading(true);
setError(null);
const res = await trigger({ id }).unwrap();
setLoading(false);
if (res) {
setStatus(res.status);
setProgress(res.progress);
if (!terminalStatuses.includes(res.status)) {
setTimeout(poll, 5000);
}
}
} catch (err) {
setLoading(false);
setError("Failed to fetch status. Retrying...");
console.error(`Polling error for ${id}:`, err);
setTimeout(poll, 10000);
}
}
poll();
return () => {
stopRef.current = true;
};
}, [id, enabled, trigger]);
const stop = () => {
stopRef.current = true;
};
return { status, progress, loading, error, stop };
}
ποΈ 2. StatusDisplay.jsx (with Start/Stop buttons + UI)
import React, { useState } from "react";
import useStatusPoller from "./useStatusPoller";
export default function StatusDisplay({
id,
globalStart = false,
globalStop = false,
}) {
const [started, setStarted] = useState(false);
const { status, progress, loading, error, stop } = useStatusPoller(
id,
started || globalStart
);
// If globalStop is triggered, stop local polling
React.useEffect(() => {
if (globalStop) {
stop();
setStarted(false);
}
}, [globalStop]);
const handleStart = () => {
setStarted(true);
};
const handleStop = () => {
stop();
setStarted(false);
};
return (
<div
style={{
marginBottom: "1rem",
border: "1px solid #ddd",
padding: "10px",
borderRadius: "6px",
backgroundColor: "#f9f9f9",
}}
>
<h4>ID: {id}</h4>
{!started && !globalStart ? (
<button onClick={handleStart}>Start</button>
) : (
<>
<button onClick={handleStop} style={{ marginBottom: "10px" }}>
Stop
</button>
<p>
Status: <strong>{status}</strong>
</p>
<progress value={progress} max="100" style={{ width: "100%" }} />
<p>{progress}%</p>
{loading && <p style={{ color: "blue" }}>Polling...</p>}
{error && <p style={{ color: "red" }}>{error}</p>}
</>
)}
</div>
);
}
π§©
3. MultiStatusPoller.jsx
(global start/stop all)
import React, { useState } from "react";
import StatusDisplay from "./StatusDisplay";
import { useGetStatusListingQuery } from "../services/chunkApi";
export default function MultiStatusPoller() {
const { data: statusListing = [] } = useGetStatusListingQuery();
const [globalStart, setGlobalStart] = useState(false);
const [globalStop, setGlobalStop] = useState(false);
const handleStartAll = () => {
setGlobalStart(true);
setGlobalStop(false);
};
const handleStopAll = () => {
setGlobalStop(true);
setGlobalStart(false);
};
return (
<div>
<div style={{ marginBottom: "20px" }}>
<button onClick={handleStartAll} style={{ marginRight: "10px" }}>
Start All
</button>
<button onClick={handleStopAll}>Stop All</button>
</div>
{statusListing.map((item) => (
<StatusDisplay
key={item.id}
id={item.id}
globalStart={globalStart}
globalStop={globalStop}
/>
))}
</div>
);
}
β What This Now Does
Feature | β Supported |
---|---|
Start individual polling | β |
Stop individual polling | β |
Start all polling | β |
Stop all polling | β |
Show loading/error state | β |
Stops polling on terminal status | β |
Let me know if you'd like to:
- Add auto-restart on error
- Track polling logs
- Show timestamps or polling history
Updated 3
- Add auto-restart on error
- Track polling logs
- Show timestamps or polling history
π§© 1.
useStatusPoller.js
(polling with Start/Stop, loading & error)
import { useState, useEffect, useRef } from "react";
import { useLazyGetStatusQuery } from "../services/chunkApi";
const terminalStatuses = ["completed", "failed"];
export default function useStatusPoller(id, enabled = false) {
const [trigger] = useLazyGetStatusQuery();
const [status, setStatus] = useState("idle");
const [progress, setProgress] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const stopRef = useRef(false);
useEffect(() => {
if (!enabled || !id) return;
stopRef.current = false;
async function poll() {
if (stopRef.current) return;
try {
setLoading(true);
setError(null);
const res = await trigger({ id }).unwrap();
setLoading(false);
setStatus(res.status);
setProgress(res.progress);
if (!terminalStatuses.includes(res.status)) {
// continue polling
setTimeout(poll, 5000);
}
} catch (err) {
setLoading(false);
setError("Polling error β retryingβ¦");
// retry after delay
setTimeout(poll, 10000);
}
}
poll();
return () => {
stopRef.current = true;
};
}, [id, enabled, trigger]);
const stop = () => { stopRef.current = true; };
return { status, progress, loading, error, stop };
}
πΌοΈ 2.
StatusDisplay.jsx
(Start/Stop UI with feedback)
import React, { useState, useEffect } from "react";
import useStatusPoller from "./useStatusPoller";
export default function StatusDisplay({ id, globalStart = false, globalStop = false }) {
const [started, setStarted] = useState(false);
const enabled = started || globalStart;
const { status, progress, loading, error, stop } = useStatusPoller(id, enabled);
useEffect(() => {
if (globalStop) {
stop();
setStarted(false);
}
}, [globalStop]);
const handleStart = () => setStarted(true);
const handleStop = () => { stop(); setStarted(false); };
return (
<div style={styles.card}>
<h4>ID: {id}</h4>
{!enabled ? (
<button onClick={handleStart}>Start</button>
) : (
<>
<button onClick={handleStop} style={styles.stopButton}>Stop</button>
<p>Status: <strong>{status}</strong></p>
<progress value={progress} max="100" style={styles.progressBar} />
<p>{progress}%</p>
{loading && <p style={styles.info}>Pollingβ¦</p>}
{error && <p style={styles.error}>{error}</p>}
</>
)}
</div>
);
}
const styles = {
card: {
border: "1px solid #ddd",
padding: "10px",
borderRadius: "6px",
marginBottom: "1rem",
backgroundColor: "#f9f9f9",
},
stopButton: {
marginBottom: "10px",
},
progressBar: {
width: "100%",
},
info: {
color: "blue",
},
error: {
color: "red",
},
};
π§° 3.
MultiStatusPoller.jsx
(global Start All / Stop All + dynamic IDs)
import React, { useState } from "react";
import StatusDisplay from "./StatusDisplay";
import { useGetStatusListingQuery } from "../services/chunkApi";
export default function MultiStatusPoller() {
const { data: statusListing = [] } = useGetStatusListingQuery();
const [globalStart, setGlobalStart] = useState(false);
const [globalStop, setGlobalStop] = useState(false);
const handleStartAll = () => {
setGlobalStart(true);
setGlobalStop(false);
};
const handleStopAll = () => {
setGlobalStop(true);
setGlobalStart(false);
};
return (
<div>
<div style={{ marginBottom: "20px" }}>
<button onClick={handleStartAll} style={{ marginRight: "10px" }}>Start All</button>
<button onClick={handleStopAll}>Stop All</button>
</div>
{statusListing.map(item => (
<StatusDisplay
key={item.id}
id={item.id}
globalStart={globalStart}
globalStop={globalStop}
/>
))}
</div>
);
}
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const chunkApi = createApi({
reducerPath: "chunkApi",
baseQuery: fetchBaseQuery({ baseUrl: "https://example.com/" }),
endpoints: (builder) => ({
getStatusListing: builder.query({
query: () => "api/statusListing",
}),
getStatus: builder.query({
query: ({ id }) => ({
url: `api/status/${id}`,
method: "GET",
}),
}),
}),
});
export const { useGetStatusListingQuery, useLazyGetStatusQuery } = chunkApi;
import { rest } from "msw";
import { faker } from "@faker-js/faker";
const statuses = ["pending", "processing", "completed", "failed"];
export const handlers = [
rest.get("https://example.com/api/statusListing", (req, res, ctx) => {
return res(ctx.json([{ id: "id1" }, { id: "id2" }, { id: "id3" }]));
}),
rest.get("https://example.com/api/status/:id", (req, res, ctx) => {
const id = req.params.id;
const chance = Math.random();
const status =
chance < 0.6
? "processing"
: chance < 0.85
? "pending"
: chance < 0.95
? "completed"
: "failed";
const progress =
status === "completed"
? 100
: status === "failed"
? 0
: faker.datatype.number({ min: 0, max: 99 });
return res(ctx.status(200), ctx.json({ id, status, progress }));
}),
];
import { setupWorker } from "msw";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);