Because the Visual Editor renders your page’s content in an iFrame and relies on client-side rendering, data fetched server-side as a prop will not be present in the Visual Editor’s preview and could cause content to be missing or broken. A great way to make sure that your client-side content is synced with your server-side content is to use our getAsyncProps helper.
Let’s say you are using Next.js with the page router and you have an API call that fetches multiple articles. Your getStaticProps function that runs on the server might look something like this:
import React from "react";
import { BuilderComponent, builder, useIsPreviewing } from "@builder.io/react";
import { getAsyncProps } from '@builder.io/utils';
// Define a function that fetches the Builder
// content for a given page
export const getStaticProps = async ({ params }) => {
// Fetch the builder content for the given page
const content = await builder
.get("page", {
userAttributes: {
urlPath: "/" + (params?.page?.join("/") || ""),
},
})
.toPromise();
await getAsyncProps(content, {
async Articles(props) {
// console.log('props',props)
return {
data: await fetch(`${yourUrlGoesHere}`).then(res => res.json()),
};
},
});
// Return the page content as props
return {
props: {
page: content || null,
},
// Revalidate the content every 5 seconds
revalidate: 5,
};
};
We already know that builder.get() is pulling in the content JSON from the appropriate page. getAsyncProps() has two arguments. The first is content, which takes in a content JSON from Builder. The next argument is a named function that returns data. getAsyncProps() looks through this process and finds every component in the content JSON that matches the name of the function in the second argument. For example, in the code above, getAsyncProps() will look through your page content returned from builder.get() for all components named “Articles” and add ‘data’.
In our case that Articles component is a custom component which we have defined below:
import React, { useState, useEffect } from 'react';
const Articles = (props) => {
const [data, setData] = useState(props.data); // Initialize state with props.data
console.log('props', data)
useEffect(() => {
if (!props.data) { // If no data is passed through props, fetch data
console.log('whoops! no data from server')
fetchResults().then(fetchedData => {
setData(fetchedData); // Update state with fetched data
console.log('data fetched on the client');
});
} else {
console.log("data is loaded");
setData(props.data);
}
}, []); // Dependency array is empty, so this effect runs only once on mount
return (<>
<div>Your Component That Uses The Data Goes Here</div>
</>);
}
export default Articles;
In an ideal situation in the browser where loading the page via SSR is not an issue, the data variable will be set by just looking at props.data. Because getAsyncProps only runs on the server, in those instances where you are only rendering client-side, for example in the Builder Visual Editor, we still need to fetch the data. You can see this in action in the useEffect above. Essentially we check if there is no data from the server, call some function (in our case called fetchResults) to fetch from the same API as we did in getAsyncProps, and use this as a backup. That way we are able to use our server side rendering on our live website for SEO and performance, and we are still able to visualize and preview our content correctly in the visual editor.
Related links: