Using Plasmic with dynamic data sources

Plasmic designs can display data from any dynamic data source, such as traditional CMSes, ecommerce backends, or application REST/GraphQL APIs. (See comparison with CMSes.)

There are two approaches to adding data into Plasmic pages: using code components, and using render overrides.

But first: when do you need to even need to do this?

When do you need dynamic data / where should content go?

Sometimes your data clearly already exists in some data source, such as an ecommerce backend or REST API backed by an application database. In that case, you will need your Plasmic design to fetch and display that data, using the techniques described in the later sections.

Other times, you’re creating new content and deciding where it should go. You should use a separate data source when you need to display the same data very differently across multiple locations. If not, you can just manage your content directly in Plasmic.

Example: blog posts

Consider blog posts.

  • There’s a listing page of all blog posts (showing their truncated snippets).
  • Each post must have its own page.
  • There may even be different category listing pages.

In each of these locations, the blog posts show content from the same source but rendered very differently—the blog listings may show a truncated snippet, while the blog detail may show hero image, more metadata about authors etc., and the full body. You may even have multiple views of the feed, such as for listing different categories of blog posts.

In this case, Plasmic can be used to create the template for the listing pages and the individual blog post page, while the content for the blog posts can live in the CMS.

Example: testimonials

For instance, say you have a set of testimonials.

If you need to display these on only your homepage, then you can just directly add the testimonials to the homepage in Plasmic Studio.

Even if you need to display the testimonials on multiple landing pages, and the testimonials need to look a bit different on each page, you can simply use Plasmic components (and variants) to do so. Components let you define a schema for the content slots, as well as display the content in different variations.

However, if you need to dynamically query/filter to different subsets of testimonials on different pages, then you should store them in a separate data source (such as a CMS) and use the techniques described in the later sections.

Approach 1: using code components (React only)

To enable your Plasmic Studio users to drag-and-drop in data from your CMS, you can create code components that let fetch and render this data.

In this approach, the workflow looks like:

  • Developer registers code components for use in Plasmic. These components would be rendering data from the CMS.
  • Plasmic Studio users create a page, dragging and dropping the components into the page.
  • Plasmic Studio users can directly publish pages. No subsequent developer involvement is necessary.

Single code component vs family of components

Let’s say you want to drop in a section of your landing page that displays product catalog data. (You can extend this example to your own situation, such as displaying a roll of blog posts from a CMS.)

One approach is to create a single component for that entire section. Content editors can drop in this whole component and customize it via its props, but not edit the design or layout of how the data is shown, as this will all be hard-coded. (This is the example shown on the code components demo site.)

Alternatively, you can create a family of components, representing individual pieces of data that Plasmic Studio users can assemble together to create custom designs or layouts of how the entire section is presented. In this scenario, you might create:

  • A ProductBox component that fetches the data and makes it available in a React Context to its slot children.
  • Components for ProductTitle, ProductImage, ProductPrice, ProductDescription, ProductAddToCartButton, etc. Users would drop these anywhere within a ProductBox to make it work.
  • Or even a generic ProductTextField component that (via a prop) lets the user choose which product data field they want to display.

This even works for repeated elements. You can create a ProductCollection component that takes its children slot contents and repeats it using repeatedElement(). This lets you display a whole collection of products, each of which is rendered using the exact arrangement of ProductTitle, ProductImage, etc. that Plasmic Studio users design.

Copy
import { repeatedElement } from '@plasmicapp/host';
const ProductCollection = ({
collectionSlug,
children,
className
}: {
children?: ReactNode;
className?: string;
collectionSlug?: string;
}) => {
const data = useFetchProductCollection(collectionSlug);
return (
<div className={className}>
{data?.productList.map((productData, i) => (
<ProductContext.Provider value={productData} key={productData.id}>
{repeatedElement(i === 0, children)}
</ProductContext.Provider>
))}
</div>
);
};
/** Or to display a single product */
const ProductBox = ({
productSlug,
children,
className
}: {
children?: ReactNode;
className?: string;
productSlug?: string;
}) => {
const data = useFetchProduct(productSlug);
return (
<div className={className}>
<ProductContext.Provider value={data?.productData}>{children}</ProductContext.Provider>
</div>
);
};
const ProductTitle = ({ className }: { className?: string }) => {
const productData = useContext(ProductContext);
return (
<div className={className}>{productData?.title ?? 'This must be inside a ProductCollection or ProductBox'}</div>
);
};
const ProductImage = ({ className }: { className?: string }) => {
// ...
};

Approach 2: using render overrides

In this approach, the workflow looks like:

  • User designs a template. This has placeholder elements for the actual dynamic data (or it defines slots).
  • User or developer makes sure the elements that need to be overridden are named.
  • Developer renders this template, and uses the component’s API to override the placeholder elements or slots with the actual dynamic data.

For instance, to use the React component render API:

Copy
const post = useBlogPost();
<PlasmicComponent
component="BlogPostPage"
componentProps={{
// Let's say these are slots.
// You can just directly pass in content into slots.
title: post.title,
author: post.author,
date: moment(post.timestamp).fromNow(),
// Let's say `body` and `heroImage` are elements.
// You can override its `children` prop to display your content.
body: {
children: post.body
},
heroImage: {
src: post.heroImage.src
}
}}
/>;

See the specific API reference for the framework you’re using for how to specify these overrides.

Static or server-rendered data fetching

The easiest way to get data-fetching components working is to make them perform their own fetching. This is easy to do client-side using useEffect() or your choice of data fetching library such as react-query or react-swr, as demonstrated above. However, for statically generated pages or server-rendered pages, you may want to ensure these components fetch data statically at pre-render time.

There are two approaches to this.

  • For any Plasmic designs that need data, the developer ensures that (in the code) the needed data is fetched statically at the page level (using the usual mechanisms such as getStaticProps for Next.js), and then rely on React Context to provide the data to any dependent components. This does require you to (redundantly) specify in the page load phase the same set of data dependencies—an issue that is not specific to Plasmic.

  • Continue having components fetch their own data, but use react-ssr-prepass or Apollo’s getDataFromTree, so that developers won’t need to manually add data-fetching to Plasmic-rendered pages.

    See a small example of using react-ssr-prepass in Next.js, or an example of using getDataFromTree (one, two).

    A key difference between these two is that getDataFromTree does incur multiple full-tree renders, while react-ssr-prepass performs fine-grained component-level re-renders by using its own React SSR renderer.

    A caveat for Next.js users is that to render your full page including any root-level context, you must use getInitialProps, which has access to AppTree. See further discussion.

    In the hopefully not-too-distant future, React 18 will introduce server-side Suspense, which will enable seamless server-side/static page-loading and deprecate approaches such as react-ssr-prepass or getDataFromTree. But for now, these libraries can be quite handy.

Another concern to be aware of is that for Plasmic Studio users who are either editing or previewing a design, the evaluation takes place client-side. So unless you want to show placeholder data (which is also fine to do), you’ll want to ensure that client-side data fetching is performed in those situations. You can use typeof window === 'undefined' to determine whether you’re in a server-side context, and rely on the fact that useEffect only runs client-side:

Copy
const fetchResult = useFetchProduct(productSlug); // Internally uses `useEffect`
const [data, isLoading] =
typeof window === 'undefined'
? [fetcher.fetch(), false] // Suspense style fetch
: fetchResult;

Note that some client libraries like React Query abstract this and can switch between Suspense and normal data fetching without requiring changes to the query sites.

Give feedback on this page