@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
90 lines (87 loc) • 3.51 kB
JavaScript
'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
};
}