Skip to content

Collection-bound Repeater renders empty in editor canvas (getBlockAsyncProps never invoked for canvas blocks) #808

Description

@Aadilhassan

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

  1. Pass collections={[{ id, name, filters, sorts }]} and a working
    getBlockAsyncProps (returns { items, totalItems }) to <ChaiBuilderEditor>.
  2. Add a Repeater with a single RepeaterItem template and bind it to a collection
    (repeaterItems = "{{#<id>}}").
  3. Do this mid-session (add/bind after mount), or view it on any page other than
    the one active at first mount
    .
  4. 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.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions