diff --git a/CHANGELOG.md b/CHANGELOG.md index 5faaa8cb21..f44e46a0dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Our versioning strategy is as follows: * `[templates/nextjs]` `[sitecore-jss-nextjs]` Better error handling for component-level data fetching ([#1586](https://github.com/Sitecore/jss/pull/1586)) * `[sitecore-jss-react]` Fetch Data for FEaaS Components as part of Component SSR/SSG ([#1586](https://github.com/Sitecore/jss/pull/1590)) * `[sitecore-jss-dev-tools]` `[templates/nextjs]` `[templates/react]` Introduce "components" configuration for ComponentBuilder ([#1598](https://github.com/Sitecore/jss/pull/1598)) +* `[sitecore-jss-react]` `[sitecore-jss-nextjs]` Component level data fetching(SSR/SSG) for BYOC ([#1610](https://github.com/Sitecore/jss/pull/1610)) ### 🧹 Chores diff --git a/packages/sitecore-jss-nextjs/src/components/BYOCWrapper.tsx b/packages/sitecore-jss-nextjs/src/components/BYOCWrapper.tsx new file mode 100644 index 0000000000..d297378b1a --- /dev/null +++ b/packages/sitecore-jss-nextjs/src/components/BYOCWrapper.tsx @@ -0,0 +1,45 @@ +import { + BYOCWrapper, + BYOCComponentParams, + fetchBYOCComponentServerProps, +} from '@sitecore-jss/sitecore-jss-react'; +import { + GetStaticComponentProps, + GetServerSideComponentProps, +} from '../sharedTypes/component-props'; +import { constants } from '@sitecore-jss/sitecore-jss'; + +/** + * This is a repackaged version of the React BYOCWrapper component with support for + * server rendering in Next.js (using component-level data-fetching feature of JSS). + */ + +/** + * Will be called during SSG + * @param {ComponentRendering} rendering + * @returns {GetStaticPropsContext} context + */ +export const getStaticProps: GetStaticComponentProps = async (rendering) => { + if (process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED) { + return null; + } + const params: BYOCComponentParams = rendering.params || {}; + const result = await fetchBYOCComponentServerProps(params); + return result; +}; + +/** + * Will be called during SSR + * @param {ComponentRendering} rendering + * @returns {GetStaticPropsContext} context + */ +export const getServerSideProps: GetServerSideComponentProps = async (rendering) => { + if (process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED) { + return null; + } + const params: BYOCComponentParams = rendering.params || {}; + const result = await fetchBYOCComponentServerProps(params); + return result; +}; + +export default BYOCWrapper; diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts index b34d35bf37..44a5ac7ef1 100644 --- a/packages/sitecore-jss-nextjs/src/index.ts +++ b/packages/sitecore-jss-nextjs/src/index.ts @@ -153,7 +153,9 @@ export { Placeholder } from './components/Placeholder'; export { EditingComponentPlaceholder } from './components/EditingComponentPlaceholder'; export { NextImage } from './components/NextImage'; import * as FEaaSWrapper from './components/FEaaSWrapper'; +import * as BYOCWrapper from './components/BYOCWrapper'; export { FEaaSWrapper }; +export { BYOCWrapper }; export { ComponentBuilder, ComponentBuilderConfig } from './ComponentBuilder'; @@ -177,7 +179,6 @@ export { BYOCComponent, BYOCComponentProps, getFEAASLibraryStylesheetLinks, - BYOCWrapper, File, FileField, RichTextField, diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx index 791a6e9570..d80beee5f4 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx @@ -12,6 +12,7 @@ describe('BYOCComponent', () => { ComponentName: 'Foo', ComponentProps: JSON.stringify({ prop1: 'value1' }), }, + fetchedData: {}, }; const Foo = () =>

Test

; FEAAS.External.registerComponent(Foo, { @@ -29,6 +30,34 @@ describe('BYOCComponent', () => { expect(fooComponent.prop('data-external-id')).to.equal('Foo'); expect(fooComponent.find('#foo-content')).to.have.length(1); }); + + it('should render when props are prefetched', () => { + const fetchedData = { + prop1: 'prefetched_value1', + }; + const mockProps = { + params: { + ComponentName: 'Foo', + ComponentProps: JSON.stringify({ prop1: 'value1' }), + }, + fetchedData, + }; + const Foo = () =>

Test

; + FEAAS.External.registerComponent(Foo, { + name: 'Foo', + properties: { + prop1: { + type: 'string', + }, + }, + }); + const wrapper = mount(); + const fooComponent = wrapper.find('feaas-external'); + expect(fooComponent).to.have.lengthOf(1); + expect(fooComponent.prop('prop1')).to.equal('prefetched_value1'); + expect(fooComponent.prop('data-external-id')).to.equal('Foo'); + expect(fooComponent.find('#foo-content')).to.have.length(1); + }); }); describe('Error handling', () => { @@ -38,6 +67,7 @@ describe('Error handling', () => { ComponentName: 'ExampleComponent', ComponentProps: 'invalid-json', }, + fetchedData: {}, }; const wrapper = mount(); const errorComponent = wrapper.find('DefaultErrorComponent'); @@ -52,6 +82,7 @@ describe('Error handling', () => { ComponentName: 'ExampleComponent', ComponentProps: 'invalid-json', }, + fetchedData: {}, }; const wrapper = mount(); @@ -96,7 +127,11 @@ describe('Error handling', () => { }); it('should render missing component frame when component is not registered', () => { - const props = { params: { ComponentName: 'NonExistentComponent' }, components: {} }; + const props = { + params: { ComponentName: 'NonExistentComponent' }, + components: {}, + fetchedData: {}, + }; const wrapper = mount(); @@ -115,6 +150,7 @@ describe('Error handling', () => { missingComponentComponent: missingComponent, params: { ComponentName: 'NonExistentComponent' }, components: {}, + fetchedData: {}, }; const wrapper = mount(); diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx index afcdf7268b..056d34ed43 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx @@ -6,6 +6,16 @@ import * as FEAAS from '@sitecore-feaas/clientside/react'; export const BYOC_COMPONENT_RENDERING_NAME = 'BYOCComponent'; +/** + * FEaaS props for server rendering. + */ +type BYOCServerProps = { + /** + * Fetched data from server component props for server rendering, based on rendering params. + */ + fetchedData?: FEAAS.DataScopes; +}; + /** * Data from rendering params on Sitecore's BYOC rendering */ @@ -13,7 +23,7 @@ export type BYOCComponentParams = { /** * Name of the component to render */ - ComponentName: string; + ComponentName?: string; /** * JSON props to pass into rendered component */ @@ -28,7 +38,7 @@ export type BYOCComponentParams = { /** * Props for BYOCComponent. Includes components list to load external components from. */ -export type BYOCComponentProps = { +export type BYOCComponentClientProps = { /** * rendering params */ @@ -50,6 +60,8 @@ export type BYOCComponentProps = { | React.FC; }; +export type BYOCComponentProps = BYOCComponentClientProps & BYOCServerProps; + type ErrorComponentProps = { [prop: string]: unknown; error?: Error; @@ -117,26 +129,30 @@ export class BYOCComponent extends React.Component { ); - let componentProps: { [key: string]: unknown } = undefined; - - if (props.params?.ComponentProps) { - try { - componentProps = JSON.parse(props.params.ComponentProps) ?? {}; - } catch (e) { - console.warn( - `Parsing props for ${componentName} component from rendering params failed. Error: ${e}` - ); - return this.props.errorComponent ? ( - - ) : ( - - ); - } - } + const ErrorComponent = this.props.errorComponent; + + const isNull = Object.keys(props.fetchedData).length === 0; + + let componentProps: { [key: string]: any } = isNull ? null : props.fetchedData; + if (!componentProps) { - componentProps = props.fields ? getDataFromFields(props.fields) : {}; + if (props.params?.ComponentProps) { + try { + componentProps = JSON.parse(props.params.ComponentProps) ?? {}; + } catch (e) { + console.error( + `Parsing props for ${componentName} component from rendering params failed. Error: ${e}` + ); + return ErrorComponent ? ( + + ) : ( + + ); + } + } else { + componentProps = props.fields ? getDataFromFields(props.fields) : {}; + } } - return ( { ); } } + +/** + * Fetches server component props required for server rendering, based on rendering params. + * @param {BYOCComponentParams} params component params + */ +export async function fetchBYOCComponentServerProps( + params: BYOCComponentParams +): Promise { + const fetchDataOptions: FEAAS.DataOptions = params.ComponentProps + ? JSON.parse(params.ComponentProps) + : {}; + + const fetchedData: FEAAS.DataScopes = await FEAAS.DataSettings.fetch(fetchDataOptions || {}); + + return { + fetchedData, + }; +} diff --git a/packages/sitecore-jss-react/src/index.ts b/packages/sitecore-jss-react/src/index.ts index e3d3dbaffe..ccdeb5fb1e 100644 --- a/packages/sitecore-jss-react/src/index.ts +++ b/packages/sitecore-jss-react/src/index.ts @@ -64,7 +64,12 @@ export { fetchFEaaSComponentServerProps, } from './components/FEaaSComponent'; export { FEaaSWrapper } from './components/FEaaSWrapper'; -export { BYOCComponent, BYOCComponentParams, BYOCComponentProps } from './components/BYOCComponent'; +export { + BYOCComponent, + BYOCComponentParams, + BYOCComponentProps, + fetchBYOCComponentServerProps, +} from './components/BYOCComponent'; export { BYOCWrapper } from './components/BYOCWrapper'; export { Link, LinkField, LinkFieldValue, LinkProps, LinkPropTypes } from './components/Link'; export { File, FileField } from './components/File';