ChaiBuilder SDK — collection-bound Repeater renders empty in the editor canvas
Package: @chaibuilder/sdk@4.0.0-beta.51
Area: live data binding / Repeater data provider / editor canvas
Summary
A Repeater bound to a collection (repeaterItems: "{{#<collectionId>}}") renders its
empty state ("Choose a collection to display items") in the editor canvas, even
though collections and a working getBlockAsyncProps are passed to
<ChaiBuilderEditor> and the block is correctly bound (the block's repeaterItems
holds {{#<id>}} and the settings panel shows the collection selected).
The same blocks render correctly through AsyncRenderChaiBlocks / the deploy/render path
— so this is specific to the live in-editor canvas preview.
Environment
@chaibuilder/sdk 4.0.0-beta.51
- React 19, Next.js 15 (editor mounted client-side via
dynamic(..., { ssr: false }))
- Default
react-frame-component canvas iframe
flags={{ dataBinding: true }}, collections and getBlockAsyncProps both provided
Reproduction
- Pass
collections={[{ id, name, filters, sorts }]} and a working
getBlockAsyncProps (returns { items, totalItems }) to <ChaiBuilderEditor>.
- Add a
Repeater with a single RepeaterItem template and bind it to a collection
(repeaterItems = "{{#<id>}}").
- Do this mid-session (add/bind after mount), or view it on any page other than
the one active at first mount.
- The canvas shows the empty state; the block never receives items.
Observations:
- A Repeater present on the initially-active page at first mount can resolve — but
only after a forced editor remount (see "Consumer workaround" below).
- Repeaters added/bound after mount, or on non-initial pages, never resolve.
- We confirmed via logging that the provided
getBlockAsyncProps is never invoked for
canvas blocks. (The only CMS network traffic we see comes from our own save/preview
code calling the CMS directly — not from the SDK's data provider.)
Root cause (two distinct issues)
1. The data-provider effect omits the resolved provider from its dependency array
In the block data-provider hook (minified eh), the provider is resolved as:
const a = G("getBlockAsyncProps", async (h) => ({})); // <- empty-object stub fallback
G resolves it reactively from the props atom:
const G = (t, e) => {
const n = useAtomValue(chaiBuilderPropsAtom); // "si"
return useMemo(() => get(n, t, e), [n, t, e]);
};
a is called inside the effect (a({ block: t }).then(...)), but the effect's
dependency array does not include a:
return useEffect(() => {
// ... e === "live" && (!u && !p || ( ... a({ block: t }).then(...) ))
}, [t?._id, d, u, p, r, e]); // <-- `a` is missing here
So when the SDK replaces the empty stub with the real getBlockAsyncProps (once
chaiBuilderPropsAtom is populated), the hook re-renders but the effect never
re-runs, and the block keeps the empty result. Its other deps (d = block id +
resolved dataProviderDependencies) also don't change, so it never retries.
Suggested fix: add the resolved provider to the effect deps, and hoist the stub
fallback to a stable module-level reference so a only changes stub→real (not on every
render, which would otherwise cause a re-fetch loop):
const CHAI_ASYNC_STUB = async () => ({});
// ...
const a = G("getBlockAsyncProps", CHAI_ASYNC_STUB);
// ...
}, [t?._id, d, u, p, r, e, a]);
2. The real getBlockAsyncProps never reaches the canvas render tree
Even with (1) applied, the canvas Repeater's provider stays the empty stub — the
consumer's getBlockAsyncProps is still never invoked for canvas blocks. The Repeater
renders inside the react-frame-component canvas iframe, and the provider config written
to chaiBuilderPropsAtom in the editor tree does not appear to be available in the
canvas render context, so G("getBlockAsyncProps", ...) resolves to the stub there.
Suggested fix: ensure chaiBuilderPropsAtom (or at least getBlockAsyncProps) is
available to / bridged into the canvas iframe render tree so the data provider resolves
inside the frame.
Expected vs actual
- Expected: the canvas Repeater resolves via
getBlockAsyncProps and renders one
RepeaterItem per resolved item, matching AsyncRenderChaiBlocks.
- Actual: the canvas Repeater renders the empty state;
getBlockAsyncProps is never
called for it.
Consumer workaround (partial)
Forcing exactly one editor remount after chaiBuilderPropsAtom is populated makes a
Repeater present at initial mount resolve. It does not help Repeaters added/bound
after mount or on non-initial pages.
ChaiBuilder SDK — collection-bound Repeater renders empty in the editor canvas
Package:
@chaibuilder/sdk@4.0.0-beta.51Area: live data binding / Repeater data provider / editor canvas
Summary
A
Repeaterbound to a collection (repeaterItems: "{{#<collectionId>}}") renders itsempty state ("Choose a collection to display items") in the editor canvas, even
though
collectionsand a workinggetBlockAsyncPropsare passed to<ChaiBuilderEditor>and the block is correctly bound (the block'srepeaterItemsholds
{{#<id>}}and the settings panel shows the collection selected).The same blocks render correctly through
AsyncRenderChaiBlocks/ the deploy/render path— so this is specific to the live in-editor canvas preview.
Environment
@chaibuilder/sdk4.0.0-beta.51dynamic(..., { ssr: false }))react-frame-componentcanvas iframeflags={{ dataBinding: true }},collectionsandgetBlockAsyncPropsboth providedReproduction
collections={[{ id, name, filters, sorts }]}and a workinggetBlockAsyncProps(returns{ items, totalItems }) to<ChaiBuilderEditor>.Repeaterwith a singleRepeaterItemtemplate and bind it to a collection(
repeaterItems = "{{#<id>}}").the one active at first mount.
Observations:
only after a forced editor remount (see "Consumer workaround" below).
getBlockAsyncPropsis never invoked forcanvas blocks. (The only CMS network traffic we see comes from our own save/preview
code calling the CMS directly — not from the SDK's data provider.)
Root cause (two distinct issues)
1. The data-provider effect omits the resolved provider from its dependency array
In the block data-provider hook (minified
eh), the provider is resolved as:Gresolves it reactively from the props atom:ais called inside the effect (a({ block: t }).then(...)), but the effect'sdependency array does not include
a:So when the SDK replaces the empty stub with the real
getBlockAsyncProps(oncechaiBuilderPropsAtomis populated), the hook re-renders but the effect neverre-runs, and the block keeps the empty result. Its other deps (
d= block id +resolved
dataProviderDependencies) also don't change, so it never retries.Suggested fix: add the resolved provider to the effect deps, and hoist the stub
fallback to a stable module-level reference so
aonly changes stub→real (not on everyrender, which would otherwise cause a re-fetch loop):
2. The real
getBlockAsyncPropsnever reaches the canvas render treeEven with (1) applied, the canvas Repeater's provider stays the empty stub — the
consumer's
getBlockAsyncPropsis still never invoked for canvas blocks. The Repeaterrenders inside the
react-frame-componentcanvas iframe, and the provider config writtento
chaiBuilderPropsAtomin the editor tree does not appear to be available in thecanvas render context, so
G("getBlockAsyncProps", ...)resolves to the stub there.Suggested fix: ensure
chaiBuilderPropsAtom(or at leastgetBlockAsyncProps) isavailable to / bridged into the canvas iframe render tree so the data provider resolves
inside the frame.
Expected vs actual
getBlockAsyncPropsand renders oneRepeaterItemper resolved item, matchingAsyncRenderChaiBlocks.getBlockAsyncPropsis nevercalled for it.
Consumer workaround (partial)
Forcing exactly one editor remount after
chaiBuilderPropsAtomis populated makes aRepeater present at initial mount resolve. It does not help Repeaters added/bound
after mount or on non-initial pages.