Recent searches

in

Visual Data Bindings with React

Last modified: October 9th, 2026

On this page

Visual Data Bindings are deprecated.

Visual Data Bindings have been deprecated since October 2025. CloudCannon continues to maintain them and fix critical bugs, but they receive no new features or further development. Existing Sites relying on this configuration will keep working. For new Sites, we recommend Editable Regions.

Visual Data Bindings use HTML attributes to tell CloudCannon which element on your page corresponds to which piece of data, allowing you to click on and edit data in the Visual Editor.

React passes data- attributes straight through to the DOM, so adding data-cms-bind to an element works the same as it does in plain HTML.CloudCannon draws the same editing overlays over it in the Visual Editor. For the attribute's syntax, please read our documentation on what Visual Data Bindings are.

The part React adds is threading the path. A binding names a value's full path from the root of your data, but a reusable component has no way of knowing where in that structure it has been placed. The component above it does, so pass the path down as a prop.

Visual Data Bindings cannot re-render the value once you change the data behind them. For that, please read our documentation on using Visual Data Previews with React.

Pass binding paths through components#

Give each component a prop holding its own path, and build child bindings from it.

JSX
Copied to clipboard
function Hero({ path, data }) {
  const bind = (key) => [path, key].filter(Boolean).join('.'); 

  return (
    <section>
      <h1 data-cms-bind={bind('title')}>{data.title}</h1>
      <p data-cms-bind={bind('subtitle')}>{data.subtitle}</p>
    </section>
  );
}

Joining with filter(Boolean) drops an empty path, so a component placed at the root produces title rather than .title.

The parent passes the path it occupies, and a component rendered at the root of your data passes an empty string.

JSX
Copied to clipboard
<Hero path="hero" data={page.hero} />
<Hero path="" data={page} /> 

An empty path is a valid value to pass down, because bind() discards it when building child bindings.

Never render an empty binding#

An empty path is fine to pass as a prop, but it must never reach the DOM as an attribute. CloudCannon treats data-cms-bind="" as a binding it cannot resolve: the element still gets an overlay in the Visual Editor, bound to a field named unset that does not exist in your data.

Render the attribute only when the component has a path.

JSX
Copied to clipboard
function Hero({ path, data }) {
  const bind = (key) => [path, key].filter(Boolean).join('.');

  return ( 
    <section {...(path ? { 'data-cms-bind': path } : {})}>
      <h1 data-cms-bind={bind('title')}>{data.title}</h1>
    </section>
  );
}

Spreading a conditional object adds the attribute when path has a value and omits it entirely when path is empty.

Arrays#

Every binding you nest adds a click, because a child binding only becomes selectable once its parent is selected. Where you have an array component rendering an item component, binding both means two clicks to reach an item.

Bind the items and leave the array container unbound. The items keep their reordering and removal controls, and a Organization Member reaches them one click sooner.

Related Resources

Open in a new tab