diff --git a/README.md b/README.md
index 6364a45..31d2cbc 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ npm install @artcom/mqtt-topping-react
### MqttProvider
-Wrap your application with `MqttProvider`. You can provide a `suspenseFallback` to handle the initial connection state automatically.
+Wrap your application with `MqttProvider`. When you provide a `suspenseFallback`, the provider will suspend rendering its children until the initial MQTT connection succeeds and show the fallback in the meantime.
```tsx
import { MqttProvider } from "@artcom/mqtt-topping-react"
@@ -137,5 +137,5 @@ function MyComponent() {
| `options` | `MqttClientOptions` | Optional configuration for the MQTT client |
| `httpBrokerUri` | `string` | Optional URI for the HTTP interface of the broker |
| `httpOptions` | `HttpClientOptions` | Optional configuration for the HTTP client |
-| `suspenseFallback` | `ReactNode` | Optional fallback UI to show while connecting |
+| `suspenseFallback` | `ReactNode` | Optional fallback UI shown while the initial MQTT connection is pending |
| `children` | `ReactNode` | Child components |
diff --git a/src/MqttProvider/MqttProvider.tsx b/src/MqttProvider/MqttProvider.tsx
index a52defc..2a450d1 100644
--- a/src/MqttProvider/MqttProvider.tsx
+++ b/src/MqttProvider/MqttProvider.tsx
@@ -4,8 +4,9 @@ import {
type MqttClientOptions,
} from "@artcom/mqtt-topping"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
-import React, { useMemo } from "react"
+import React, { Suspense, useMemo } from "react"
+import { useMqttSuspense } from "../useMqttSuspense"
import { MqttContext } from "./MqttContext"
import { useMqttConnection } from "./useMqttConnection"
@@ -14,11 +15,17 @@ export interface MqttProviderProps {
options?: MqttClientOptions
httpBrokerUri?: string
httpOptions?: HttpClientOptions
+ suspenseFallback?: React.ReactNode
children: React.ReactNode
}
const defaultQueryClient = new QueryClient()
+function MqttSuspenseBoundary({ children }: { children: React.ReactNode }) {
+ useMqttSuspense()
+ return <>{children}>
+}
+
/**
* Provider component for MQTT context.
* Manages the MQTT connection and provides the client instance to children.
@@ -36,6 +43,7 @@ export function MqttProvider({
options,
httpBrokerUri,
httpOptions,
+ suspenseFallback,
children,
}: MqttProviderProps) {
const stableOptions = useMemo(() => options, [options])
@@ -65,7 +73,13 @@ export function MqttProvider({
connectionPromise,
}}
>
- {children}
+ {suspenseFallback ? (
+
+ {children}
+
+ ) : (
+ children
+ )}
)
diff --git a/tests/MqttProvider.test.tsx b/tests/MqttProvider.test.tsx
new file mode 100644
index 0000000..fcf497f
--- /dev/null
+++ b/tests/MqttProvider.test.tsx
@@ -0,0 +1,67 @@
+import { MqttClient } from "@artcom/mqtt-topping"
+import { render, screen, waitFor } from "@testing-library/react"
+import { beforeEach, describe, expect, it, vi } from "vitest"
+
+import { MqttProvider } from "../src/MqttProvider/MqttProvider"
+
+vi.mock("@artcom/mqtt-topping", () => {
+ return {
+ HttpClient: vi.fn().mockImplementation(() => ({ queryJson: vi.fn() })),
+ MqttClient: {
+ connect: vi.fn(),
+ },
+ }
+})
+
+describe("MqttProvider", () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ it("renders suspenseFallback while the MQTT connection is pending", async () => {
+ let resolveConnection!: (client: MqttClient) => void
+ const connectionPromise = new Promise((resolve) => {
+ resolveConnection = resolve
+ })
+
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ vi.mocked(MqttClient.connect).mockReturnValue(connectionPromise)
+
+ const disconnect = vi.fn()
+
+ render(
+ Connecting to MQTT...}
+ >
+ Ready
+ ,
+ )
+
+ expect(screen.getByText("Connecting to MQTT...")).toBeInTheDocument()
+ expect(screen.queryByText("Ready")).not.toBeInTheDocument()
+
+ resolveConnection({ disconnect } as unknown as MqttClient)
+
+ await waitFor(() => {
+ expect(screen.getByText("Ready")).toBeInTheDocument()
+ })
+ })
+
+ it("renders children immediately when suspenseFallback is not provided", () => {
+ const neverSettlesPromise = new Promise((_resolve) => {
+ // Intentionally unresolved: keeps the connection in-flight for this assertion.
+ })
+
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ vi.mocked(MqttClient.connect).mockReturnValue(neverSettlesPromise)
+
+ render(
+
+ Ready
+ ,
+ )
+
+ expect(screen.getByText("Ready")).toBeInTheDocument()
+ })
+})