Using React with Headless API

The React API for working with the Headless API includes three main things:

  • PlasmicComponentLoader instance that is used to load design data from Plasmic.
  • <PlasmicRootProvider /> component that sits near the root of your React application, and provides a place for you to inject pre-fetched data, as well as specify global variants.
  • <PlasmicComponent /> component that renders a component designed in Plasmic; allows you to configure what variants to target, slot contents to use, and event handlers to add.

PlasmicComponentLoader

An instance of PlasmicComponentLoader is returned by initPlasmicLoader(). This should be a global singleton in your app; it can be passed into the <PlasmicRootProvider/> component, and be used to dynamically fetch component data.

initPlasmicLoader()

This function takes in an object with these keys as the argument:

ArgumentDescription
projectsA list of Plasmic projects you are using for your application. Each project should look like: an object with an id and a token field. You can also optionally pass in a version field to pin it to a specific published version.

To find your project’s public API token: open the project in Plasmic Studio, and click the Code toolbar button.

previewBy default, Plasmic will always use the most recently published version of your projects. If instead you want to see the latest unpublished changes in your projects, you can pass in the preview: true. Note that this should only be used for development! Learn more about supporting previews in your app.

Specifying project version

By default, PlasmicComponent will fetch and render the latest published version of your project. These published versions have been optimized and will be served over CDNs.

If you want a specific published version instead of the latest published version, you can explicitly specify it like so:

Copy
export const PLASMIC = initPlasmicLoader({
projects: [
{
id: '...',
token: '...',
version: '2.1.3'
}
]
});

Previewing latest changes during development

During development, however, you may wish to render the latest unpublished changes, instead of the latest published version, so you can see changes in your development server as you make updates in Plasmic studio. In that case, you can pass a preview flag:

Copy
export const PLASMIC = initPlasmicLoader({
projects: [
{
id: '...',
token: '...'
}
],
preview: true
});

Note that preview: true will take longer and serve less optimized code! Only use this for development.

PlasmicComponentLoader.fetchComponentData()

This method fetches data needed to render the specified components. It takes any number of arguments; each argument can be either:

  • A string corresponding to either the name of a component or the path of a page
  • An object {name: 'name_or_path', projectId: '...'} if there are multiple components of the same name across different projects.

For example,

Copy
const data = await PLASMIC.fetchComponentData('/', 'LoginForm');

The specified components are ‘entrypoints’; components that are used by those entrypoints are automatically included, so you don’t need to provide an exhaustive list of components to fetch.

If you’ve specified a component name that Plasmic doesn’t know about, an error is thrown.

This function returns an object like this:

Copy
{
entryCompMetas: [
// There's one object here for each entrypoint you
// specified in your call to fetchComponentData()
{
id: "...", // unique identifier
name: "ComponentName", // normalized name of component
isPage: true, // true if it corresponds to a page
pageMetadata: {
// This object exists if isPage is true
path: "/page", // path for this page
// The page metadata below can be used to embed into your
// <head/> tag
title: "My Page", // title of the page if set
description: "This is me!", // description of the page if set
openGraphImageUrl: "...", // url of open graph image
}
}
],
remoteFontUrls: [
// Remote URLS that can be used to load fonts that you need
// in your projects. By default, `PlasmicComponent` will
// already include the fonts you need, but you can use this
// information to try to pre-load them in your site if
// you'd like.
"https://fonts.google.com/..."
]
}

PlasmicComponentLoader.maybeFetchComponentData()

Same as fetchComponentData(), except if you specify a component name that Plasmic doesn’t know about, null is returned. This is useful in catch-all pages, where you’re not sure if an arbitrary path string corresponds to a Plasmic page or not.

PlasmicComponentLoader.fetchPages()

This fetches all pages that you have defined across your initialized set of projects. This is useful when you want to retrieve a list of pages you want to statically pre-generate HTML for.

PlasmicComponentLoader.substituteComponent()

This function registers an arbitrary React component to swap with a Plasmic component. You can use this to substitute in a real component for a stand-in Plasmic component.

For example,

Copy
import Slider from '@material-ui/core/Slider';
// Swap in the real material UI slider
PLASMIC.substituteComponent(Slider, 'Slider');

Unlike .registerComponent(), this does not affect the Plasmic Studio editing experience.

PlasmicComponentLoader.registerComponent()

This function registers an arbitrary React component for use within Plasmic Studio. Plasmic Studio editors will then be able to drag-and-drop these “code components” into their pages and configure their props.

For example,

Copy
PLASMIC.registerComponent(ProductCard, {
name: 'ProductCard',
props: {
children: 'slot',
columns: 'number',
animate: {
type: 'boolean',
defaultValue: true
},
category: {
type: 'choice',
options: ['success', 'warning', 'error']
}
}
});

For code components to work, you need to enable app-hosted Plasmic. See the code components docs and its own API reference to learn more.

<PlasmicRootProvider />

This component should sit near the root of your application. It provides information to the <PlasmicComponent /> components you have throughout your app.

It takes these props:

PropDescription
loaderinstance of PlasmicComponentLoader you created via initPlasmicLoader()
prefetchedDataIf you have prefetched data via fetchComponentData(), you can specify the returned data as prop here; <PlasmicComponent /> will avoid fetching data dynamically if it can find the data it needs in the prefetched data.
globalVariantsYou can activate the Plasmic global variants, like globalVariants={[{name: 'Theme', value: 'dark'}]}
skipCssIf true, will not include css for components. You may want to do is if you are for some reason nesting PlasmicRootProviders; in that case, you'd only need css injected by the outermost PlasmicRootProvider.
skipFontsIf true, will not include @import css to load remote fonts. You may want to do this if you are already loading the fonts you need yourself.

<PlasmicComponent />

This component renders a specific component designed in Plasmic. If the data is available (either already fetched or is provided as prefetchedData for <PlasmicRootProvider/>), then it is rendered immediately; otherwise, it renders null while it fetches data.

PropDescription
componentEither the name of the component or the path of the page component
projectIdIf there are multiple components of the same name across projects you are using, you can pass this in to disambiguate
componentPropsProps to pass to the component to activate variants, specify slot contents, or attach event handlers. See here for details.
forceOriginal

If you used registerComponent(), then if the name matches a registered component, that component is used. If you want the Plasmic-generated component instead, specify forceOriginal. You usually want this when you are doing component substitution to attach behavior and state to Plasmic-generated components..

Customizing your component with componentProps

When you render a component using <PlasmicComponent />, you can use the componentProps prop to control which variants to activate, which slots to fill with what content, and which elements to instrument with real data or event handlers.

There are four classes of props that you can pass to componentProps, described in the next sections.

Variant props

Component variants are either simple standalone “toggle” variants, or they are organized into groups. For example, in a Button component, we may have a variant group role that includes primary and secondary variants, or a size group that includes small and large variants. Often the groups are single-choice — you only allow one variant per group to be active. But sometimes it makes sense for them to be multi-choice — for example, a Button component may have a variant group withIcons that has options prefix and suffix, both of which can be true at the same time.

Each toggle variant and each variant group is a prop.

  • For toggle variants, you can pass in true if you want it activated.
  • For single-choice variant groups, you can pass in the name of the variant you want to activate, or undefined if none should be activated.
  • For multi-choice variant groups, you can pass in an array of variant names, or a object of variant names mapping to true or false.

Example:

Copy
// Passing in booleans to turn on `isLoading` variant.
<PlasmicComponent
component="Button"
componentProps={{
isLoading: true
}}
/>
// Passing in literals to turn on `role` and `withIcons` variants
<PlasmicComponent
component="Button"
componentProps={{
role: "primary",
withIcons: ["prefix", "suffix"]
}}
/>
// Turning on variants conditionally
<PlasmicComponent
component="Button"
componentProps={{
role: (
isPrimary() ? 'primary' :
isSecondary() ? 'secondary' :
undefined
),
withIcons:{
prefix: hasPrefixIcon(),
suffix: hasSuffixIcon()
}
}}
/>

The variants prop

You can also combine all the variants you want to activate into a single variants key:

Copy
<PlasmicComponent
component="Button"
componentProps={{
variants: {
role: isPrimary() ? 'primary' : isSecondary() ? 'secondary' : undefined,
withIcons: {
prefix: hasPrefixIcon(),
suffix: hasSuffixIcon()
}
}
}}
/>

Slot Props

You can also specify content for slots defined for the component. For example, a Button component might have slots that correspond to the button text (children), the prefix icon, and the suffix icon:

Copy
<PlasmicComponent
component="Button"
componentProps={{
prefixIcon: <TrashIcon />,
children: 'Hello!'
}}
/>

You can also combine all the slot content you want to use into a single args key:

Copy
<PlasmicComponent
component="Button"
componentProps={{
args: {
prefixIcon: ...,
suffixIcon: ...,
children: "Hello!"
}
}}
/>

Override props

The component as designed in Plasmic creates a tree of elements that corresponds to the tree of layers you see in Plasmic. The override props allow you to customize this tree of elements exactly as you see fit to make your component come alive. You can modify the props used for each element, attach event handlers, override rendered content, wrap elements in other React components, and more. We want you to have total control in making the element tree exactly what you want.

You reference the element you want to override by its name; so if you want to override an element, you must first name that element in Plasmic.

For example, for the Button component, you might want to override its root element to attach click handlers:

Copy
<PlasmicComponent
component="Button"
componentProps={{
root: {
props: {
onClick: () => alert('I got clicked!')
}
}
}}
/>

Or maybe you want to render the Button as a link instead:

Copy
<PlasmicComponent
component="Button"
componentProps={{
root: {
as: 'a',
props: {
href: 'https://plasmic.app'
}
}
}}
/>

The object you pass into the named node (“root”) above is the “Override object”. The Override object supports the following properties; you can mix and match them as needed:

props

Object of props to use for that element. Note that the element may be a normal HTML tag — in which case you can pass in HTML attributes — or a component — in which case you can pass in component props. For example,

Copy
{
props: {
title: user.name,
onClick: () => ...,
// etc.
}
}

as

The React.ElementType to use to render this element. This can be an HTML tag like “a” for links, or a React component. This element will then be rendered with that element type. For example,

Copy
{
as: "a",
props: {
href: ...
}
}

render

A function that takes in the props and the component type that would be rendered, and returns a ReactNode. Doing this will completely replace this element with whatever is returned from the render function.

For example, this is a no-op (will behave as if no render function was specified):

Copy
{
render: (props, Component) => <Component {...props} />;
}

You can adjust the rendering in various ways:

Copy
{
render: (props, Component) => (
<Wrapper>
<Component className={props.className} value={value} onChange={handleChange} />
</Wrapper>
);
}

You can swap in completely different content for this element:

Copy
{
render: (props, Component) => <TotallyDifferentComponent />;
}

You can also return null if you don’t want to render this element at all:

Copy
{
render: (props, Component) => null;
}

If you pass in a render function, then props and as are ignored.

wrap

A function that takes in the rendered ReactNode for this element, and returns another ReactNode. This is useful if you just want to wrap this element in something, like a context provider. For example,

Copy
{
wrap: node => <Context.Provider value={}>{node}</Context.Provider>
}

wrapChildren

A function that takes in the rendered children for this element, and returns another ReactNode. This is useful if you want to append or prepend some custom content as a child of this element. For example, to implement an accessible “checkbox”, you may want to sneak in a visually hidden input element that’s not actually in the design:

Copy
{
// insert a visually hidden checkbox input as the first child
wrapChildren: (children) => (
<>
<input className="visually-hidden" type="checkbox" />
{children}
</>
);
}

If you are planning to iterate over children, note that Plasmic-generated code will sometimes wrap elements in React.Fragments. Therefore, instead of using React.Children.map(), you should consider using something like react-keyed-flatten-children, which will flatten fragments for you.

Override object shorthands

Instead of passing in a full Override object as specified above, you can instead use one of these shorthands:

  • An object of prop overrides — if you pass in an object without any of the known keys above (props, as, render, wrap, and wrapChildren), we interpret that object as just a props override, equivalent to passing in {props: …}.
  • A ReactNode — we interpret this as the children override, equivalent to {props: {children: …}}.
  • A function — we interpret this as the render override, equivalent to {render: …}.

Root override props

Any additional props you pass into componentProps are interpreted as an override for the root element. For example, instead of using the root prop as we did before, we can directly set the overrides like this:

Copy
<PlasmicComponent
component="Button"
// This is interpreted as a prop override for the `root` element
componentProps={{ onClick: () => alert('I got clicked!') }}
/>

Exploring the componentProps you can use

You can explore exactly what componentProps you can use for each component in Plasmic’s API explorer. You can get there by going to your project in Plasmic, clicking on the “Code” button in the upper-right, and selecting “Loader” as the integration scheme.

Give feedback on this page