Contents

Custom Props and Click Handlers

Pass your own props — an onClick above all — from your app to one component inside a widget.

What this is for

Your author builds a call-to-action button in the composer. You need clicking it to do something only your application can do — open a booking drawer, start a checkout, fire an analytics event. The composer has no way to know about any of that, and you should not have to re-create the button in your own code to get a handler onto it.

componentProps closes that gap. You pass a small map of extra props to RuleCMSWidget, addressed at one component inside the widget, and the widget hands them to it while rendering.

You need one value: the component id of the component you want to reach. Open the widget in the composer, click the component, and the Modify drawer shows its id at the top with a Copy button.

Step 1 — Copy the component id

  1. Open the widget in the composer.
  2. Click the component you want your app to control. The Modify drawer opens.
  3. At the top of the drawer, under Component id, click Copy.

The id belongs to that component and does not change when the author edits its label, styles, or position. It does change if the component is deleted and added again — that is a new component, with a new id.

Step 2 — Pass a prop from your app

Each entry in componentProps is keyed by a component id. Whatever you put in the entry is handed to that component.

'use client';

import { useCallback } from 'react';
import { RuleCMSWidgetProvider, RuleCMSWidget } from '@rulecms/widget-react';
import * as sourceComponents from '@rulecms/source-components-react';

const CTA_COMPONENT_ID = 'b721c4e2-8f0a-4c31-9a77-1d5e3f0b2c44';

export function PricingSection() {
  const openBookingDrawer = useCallback(() => {
    // your app's code
  }, []);

  return (
    <RuleCMSWidgetProvider
      token={process.env.NEXT_PUBLIC_RULECMS_TOKEN}
      libraries={{ default: sourceComponents }}
    >
      <RuleCMSWidget
        publishedKey="…---widget-…"
        componentProps={{ [CTA_COMPONENT_ID]: { onClick: openBookingDrawer } }}
      />
    </RuleCMSWidgetProvider>
  );
}

That is the whole API. The component keeps everything the author configured — its label, its preset, its column styles — and gains the behaviour you supplied.

What each component accepts

RuleCMS passes your props along; it does not interpret them. Each component decides which ones it understands, so the keys that do something depend on what you are addressing.

ComponentPropWhat it does
ButtononClickCalled with the click event when the button is clicked.

Passing a prop a component does not read is harmless — nothing happens. That is deliberate, so a key left behind after an author reworks a widget cannot break your page. It also means a typo is silent, which is what the next section is about.

Buttons that link somewhere

If the author also set a Link on the button, your handler runs and the browser then follows the link. To handle the click yourself instead, cancel the navigation:

const onClick = useCallback((event) => {
  event.preventDefault();
  openBookingDrawer();
}, [openBookingDrawer]);

Buttons the author disabled

A disabled button never calls your handler. Disabled is the author's decision and it wins — there is no way to click through it from your code.

When a key matches nothing

A key that addresses no component in the widget does nothing at all, and the page looks fine. While running outside production your browser console names the unmatched keys so you can catch it during development:

RuleCMSWidget "…---widget-…": componentProps "b721c4e2-…" matches no
component in this widget, so those props were not passed to anything.

Usual causes: the id was copied from a different widget, the component was deleted and re-added, or the id has a stray character. Re-copy it from the Modify drawer.

Components inside a collection

A collection is a group of components that can be embedded in several widgets, or more than once in the same widget. Every copy of it contains the same components, with the same ids — so a component id from inside a collection reaches every copy.

That is often what you want. When it is not, narrow the key by putting the id of the column holding the collection in front of it, separated by a slash:

componentProps={{
  // every copy of the collection
  'inner-component-id': { onClick: trackClick },
  // only the copy embedded at this spot
  'embedding-id/inner-component-id': { onClick: openBookingDrawer },
}}

To get the embedding id, click the collection itself in the composer (not a component inside it) — the drawer labels its id Embedding id. When both a plain id and a narrowed path match the same component, the narrowed one wins.

Server-rendered pages

RuleCMSWidgetServer cannot take componentProps. It is a React Server Component, and a function cannot be passed from the server to the browser.

To keep server-rendered HTML and still get a working handler, fetch the widget on the server and render it from a client component:

// page.tsx — Server Component
import { fetchRuleCMSWidget } from '@rulecms/widget-react/server';
import { PricingSection } from './PricingSection';

export default async function Page() {
  const initialData = await fetchRuleCMSWidget({ publishedKey, token });
  return <PricingSection publishedKey={publishedKey} initialData={initialData} />;
}
// PricingSection.tsx
'use client';

import { RuleCMSWidget } from '@rulecms/widget-react';

export function PricingSection({ publishedKey, initialData }) {
  return (
    <RuleCMSWidget
      mode="pre-fetched"
      publishedKey={publishedKey}
      initialData={initialData}
      libraries={{ default: sourceComponents }}
      componentProps={{ [CTA_COMPONENT_ID]: { onClick: openBookingDrawer } }}
    />
  );
}

The HTML embed cannot use this either: its API is HTML attributes, which cannot carry a function. Attach your own listener to the surrounding element instead.

Things worth knowing

QuestionAnswer
Does the composer run my handler?No. Authors editing the widget never trigger your application code.
When does the handler start working?Once the page has loaded your JavaScript. A click in the moment before that does nothing, as with any React handler.
Can I pass values, not just functions?You can pass anything, but a component only reacts to the props it documents. Content the author should control belongs in the composer, not here.
Will my keys break when the author edits?Editing a component keeps its id. Deleting and re-adding it does not — re-copy the id from the drawer.

One performance note: define your handlers with useCallback or outside the component. A fresh object on every render makes the widget re-render along with it.

Related docs