Skip to content

Multi-ID Status Tracker with RTK polling

❌ The issue is this line:
const res = await trigger(id, true).unwrap();

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:

- const res = await trigger(id, true).unwrap();
+ const res = await trigger({ id }).unwrap();

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:

getStatus: builder.query({
  query: ({ id }) => ({
    url: `api/status/${id}`,
    method: "GET",
  }),
}),

And you're exporting it:

export const {
  useLazyGetStatusQuery,
  useGetStatusListingQuery,
  // other hooks...
} = chunkApi;

βœ… Test and verify:

  • When you click "Start" for an ID, it should now call trigger({ id }), get the status, and keep polling until completed or failed.

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 or failed

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>
    );
}
src/services/chunkApi.js
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;
src/mocks/handlers.js
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 }));
  }),
];
src/mocks/browser.js
import { setupWorker } from "msw";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);

Reference