UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

90 lines (87 loc) 3.51 kB
'use client'; import * as React from 'react'; import { createSettleGate } from "../useCoordinated/createSettleGate.mjs"; import { CoordinatedGateContext } from "../CoordinatedLazy/CoordinatedGateContext.mjs"; import { jsx as _jsx } from "react/jsx-runtime"; /** * Scope a group of chunks so the page can tell when they have all loaded. * * Returns a `Controller` provider to wrap the chunks in - it supplies the * controller's gate as the ambient gate (via `CoordinatedGateContext`), so * chunks rendered inside register their swap with it through `CoordinatedLazy` * without a `gate` prop - and a reactive `loading` flag that stays `true` until * every registered chunk settles. Completion resolves via the gate's * **known-count** (`knownCount`) or **last-chunk** (`streaming` + `markLast`) * signals; with neither, it opens as soon as the chunks present in the initial * commit all settle. Each chunk also registers with the page-global gate (via * `CoordinatedLazy`), so a page-wide coordinated commit waits for them too. */ export function useStreamController(options = {}) { const { knownCount, streaming = false, safetyTimeoutMs } = options; // One gate per controller instance, configured once with its completion mode. const [gate] = React.useState(() => { const instance = createSettleGate(safetyTimeoutMs != null ? { safetyTimeoutMs } : undefined); if (knownCount != null) { instance.expect(knownCount); } else if (streaming) { // Hold open for an unknown-count stream until markLast. instance.expect(Number.POSITIVE_INFINITY); } return instance; }); // Stable provider that hands this instance's gate down as the ambient gate // (created once; `gate` is stable for the controller's lifetime). const [Controller] = React.useState(() => function StreamControllerProvider({ children }) { return /*#__PURE__*/_jsx(CoordinatedGateContext.Provider, { value: gate, children: children }); }); const [loading, setLoading] = React.useState(() => knownCount !== 0); React.useEffect(() => { // Chunks register in their own (child) effects, which run before this // (parent) effect - so by now the gate reflects every chunk present in the // initial commit. These setState calls cannot move to render: at render // time the gate is still unarmed, so `gate.isSettled()` returns `true` and a // render-time derivation would wrongly report 'done' immediately. Only after // the child effects register does the gate reflect the initial-commit chunks. /* eslint-disable react-hooks/set-state-in-effect -- gate.isSettled() only reflects child chunks after their (child) effects register them; this parent effect runs after, so the value is unavailable during render */ if (gate.isSettled()) { setLoading(false); return undefined; } setLoading(true); let cancelled = false; const wait = gate.whenSettled(); if (wait) { wait.then(() => { if (!cancelled) { setLoading(false); } }).catch(() => {}); } else { setLoading(false); } /* eslint-enable react-hooks/set-state-in-effect */ return () => { cancelled = true; }; }, [gate]); const markLast = React.useCallback(() => gate.markLast(), [gate]); const setKnownCount = React.useCallback(count => gate.expect(count), [gate]); return { Controller, loading, gate, markLast, setKnownCount }; }