From 956c9d4f4f3e5d58b3d822afe8d2fb88192092e1 Mon Sep 17 00:00:00 2001 From: Addy Pathania Date: Fri, 8 Sep 2023 11:47:24 -0400 Subject: [PATCH 1/9] added component level fetching for byoc --- .../src/components/BYOCWrapper.tsx | 45 ++++++++++ packages/sitecore-jss-nextjs/src/index.ts | 3 +- .../src/components/BYOCComponent.tsx | 87 +++++++++++++++---- 3 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 packages/sitecore-jss-nextjs/src/components/BYOCWrapper.tsx 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.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx index afcdf7268b..734e6fdc8f 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx @@ -6,14 +6,24 @@ 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 */ -export type BYOCComponentParams = { +export type BYOCComponentParams = BYOCServerProps & { /** * Name of the component to render */ - ComponentName: string; + ComponentName?: string; /** * JSON props to pass into rendered component */ @@ -119,30 +129,75 @@ 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 ? ( - - ) : ( - - ); + if (props.params.fetchedData === null) { + 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 ? ( + + ) : ( + + ); + } } } - if (!componentProps) { + if (!componentProps && props.params.fetchedData === null) { componentProps = props.fields ? getDataFromFields(props.fields) : {}; } + const data = props.params.fetchedData || componentProps || {}; + return ( ); } } + +/** + * @param {BYOCComponentParams} params rendering parameters for BYOC component + */ +export async function fetchBYOCComponentServerProps( + params: BYOCComponentParams +): Promise { + let fetchedData: FEAAS.DataScopes = null; + const fetchDataOptions: FEAAS.DataOptions = params.ComponentProps + ? JSON.parse(params.ComponentProps) + : {}; + + try { + fetchedData = await fetchData(fetchDataOptions); + } catch (e) { + console.error(e); + } + + return { + fetchedData, + }; +} + +/** + * Fetches component data based on the provided data options. + * This function asynchronously fetches data using the FEAAS.DataSettings.fetch method. + * + * @param {FEAAS.DataOptions} dataOptions - Options to customize data fetching. + * @returns {Promise} A promise that resolves with the fetched data, + * or rejects with an error if data fetching encounters an issue. + * @throws {Error} If an error occurs during data fetching, it is propagated as an error. + */ +async function fetchData(dataOptions: FEAAS.DataOptions): Promise { + try { + const fetchedData = await FEAAS.DataSettings.fetch(dataOptions || {}); + return fetchedData; + } catch (error) { + console.error('Fetch BYOC component props failed'); + throw error; + } +} From 9eb8e9077144a8eb415a9a38bc404e210755afaa Mon Sep 17 00:00:00 2001 From: Addy Pathania Date: Mon, 11 Sep 2023 17:43:09 -0400 Subject: [PATCH 2/9] added server props types --- .../src/components/BYOCComponent.tsx | 14 ++++++++------ packages/sitecore-jss-react/src/index.ts | 7 ++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx index 734e6fdc8f..3f278115a6 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx @@ -19,7 +19,7 @@ type BYOCServerProps = { /** * Data from rendering params on Sitecore's BYOC rendering */ -export type BYOCComponentParams = BYOCServerProps & { +export type BYOCComponentParams = { /** * Name of the component to render */ @@ -38,7 +38,7 @@ export type BYOCComponentParams = BYOCServerProps & { /** * Props for BYOCComponent. Includes components list to load external components from. */ -export type BYOCComponentProps = { +export type BYOCComponentClientProps = { /** * rendering params */ @@ -60,6 +60,8 @@ export type BYOCComponentProps = { | React.FC; }; +export type BYOCComponentProps = BYOCComponentClientProps & BYOCServerProps; + type ErrorComponentProps = { [prop: string]: unknown; error?: Error; @@ -129,7 +131,7 @@ export class BYOCComponent extends React.Component { let componentProps: { [key: string]: unknown } = undefined; - if (props.params.fetchedData === null) { + if (props.fetchedData === null) { if (props.params?.ComponentProps) { try { componentProps = JSON.parse(props.params.ComponentProps) ?? {}; @@ -145,11 +147,11 @@ export class BYOCComponent extends React.Component { } } } - if (!componentProps && props.params.fetchedData === null) { + if (!componentProps && props.fetchedData === null) { componentProps = props.fields ? getDataFromFields(props.fields) : {}; } - const data = props.params.fetchedData || componentProps || {}; + const data = props.fetchedData || componentProps || {}; return ( { */ export async function fetchBYOCComponentServerProps( params: BYOCComponentParams -): Promise { +): Promise { let fetchedData: FEAAS.DataScopes = null; const fetchDataOptions: FEAAS.DataOptions = params.ComponentProps ? JSON.parse(params.ComponentProps) 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'; From cde4702e08358686fb45a051ce7b019e9705ec16 Mon Sep 17 00:00:00 2001 From: Addy Pathania Date: Mon, 11 Sep 2023 18:05:21 -0400 Subject: [PATCH 3/9] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5faaa8cb21..ca5e7b710f 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) for BYOC ([#1610](https://github.com/Sitecore/jss/pull/1610)) ### 🧹 Chores From 86ef38705417e406bd6851762cbdb2fe0a62d0f3 Mon Sep 17 00:00:00 2001 From: Addy Pathania Date: Mon, 11 Sep 2023 23:09:41 -0400 Subject: [PATCH 4/9] add unit test --- .../src/components/BYOCComponent.test.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx index 791a6e9570..cff79ca1c8 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx @@ -12,6 +12,35 @@ describe('BYOCComponent', () => { ComponentName: 'Foo', ComponentProps: JSON.stringify({ prop1: 'value1' }), }, + fetchedData: null, + }; + 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('value1'); + 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: 'value1', + }; + const mockProps = { + params: { + ComponentName: 'Foo', + ComponentProps: JSON.stringify({ prop1: 'value1' }), + }, + fetchedData, }; const Foo = () =>

Test

; FEAAS.External.registerComponent(Foo, { @@ -38,6 +67,7 @@ describe('Error handling', () => { ComponentName: 'ExampleComponent', ComponentProps: 'invalid-json', }, + fetchedData: null, }; const wrapper = mount(); const errorComponent = wrapper.find('DefaultErrorComponent'); @@ -52,6 +82,7 @@ describe('Error handling', () => { ComponentName: 'ExampleComponent', ComponentProps: 'invalid-json', }, + fetchedData: null, }; const wrapper = mount(); From cea622b3c82131c466e73ad3f42b05e150de2abf Mon Sep 17 00:00:00 2001 From: Addy Pathania Date: Tue, 12 Sep 2023 13:55:25 -0400 Subject: [PATCH 5/9] remove fetchData function, adjust test --- .../src/components/BYOCComponent.test.tsx | 4 +- .../src/components/BYOCComponent.tsx | 48 ++++++------------- 2 files changed, 16 insertions(+), 36 deletions(-) diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx index cff79ca1c8..88a2178957 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx @@ -33,7 +33,7 @@ describe('BYOCComponent', () => { it('should render when props are prefetched', () => { const fetchedData = { - prop1: 'value1', + prop1: 'prefetched_value1', }; const mockProps = { params: { @@ -54,7 +54,7 @@ describe('BYOCComponent', () => { const wrapper = mount(); const fooComponent = wrapper.find('feaas-external'); expect(fooComponent).to.have.lengthOf(1); - expect(fooComponent.prop('prop1')).to.equal('value1'); + 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); }); diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx index 3f278115a6..aafab2d8a5 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx @@ -129,42 +129,41 @@ export class BYOCComponent extends React.Component { ); - let componentProps: { [key: string]: unknown } = undefined; + const ErrorComponent = this.props.errorComponent; - if (props.fetchedData === null) { + let componentProps: { [key: string]: any } = props.fetchedData; + + if (!componentProps) { if (props.params?.ComponentProps) { try { componentProps = JSON.parse(props.params.ComponentProps) ?? {}; } catch (e) { - console.warn( + console.error( `Parsing props for ${componentName} component from rendering params failed. Error: ${e}` ); - return this.props.errorComponent ? ( - + return ErrorComponent ? ( + ) : ( ); } + } else { + componentProps = props.fields ? getDataFromFields(props.fields) : {}; } } - if (!componentProps && props.fetchedData === null) { - componentProps = props.fields ? getDataFromFields(props.fields) : {}; - } - - const data = props.fetchedData || componentProps || {}; - return ( ); } } /** - * @param {BYOCComponentParams} params rendering parameters for BYOC component + * Fetches server component props required for server rendering, based on rendering params. + * @param {BYOCComponentParams} params component params */ export async function fetchBYOCComponentServerProps( params: BYOCComponentParams @@ -175,31 +174,12 @@ export async function fetchBYOCComponentServerProps( : {}; try { - fetchedData = await fetchData(fetchDataOptions); + fetchedData = await FEAAS.DataSettings.fetch(fetchDataOptions || {}); } catch (e) { - console.error(e); + console.error('Fetch BYOC component props failed'); } return { fetchedData, }; } - -/** - * Fetches component data based on the provided data options. - * This function asynchronously fetches data using the FEAAS.DataSettings.fetch method. - * - * @param {FEAAS.DataOptions} dataOptions - Options to customize data fetching. - * @returns {Promise} A promise that resolves with the fetched data, - * or rejects with an error if data fetching encounters an issue. - * @throws {Error} If an error occurs during data fetching, it is propagated as an error. - */ -async function fetchData(dataOptions: FEAAS.DataOptions): Promise { - try { - const fetchedData = await FEAAS.DataSettings.fetch(dataOptions || {}); - return fetchedData; - } catch (error) { - console.error('Fetch BYOC component props failed'); - throw error; - } -} From f42706dad387b39a0c5e564405f7927201eccb5e Mon Sep 17 00:00:00 2001 From: Addy Pathania <89087450+sc-addypathania@users.noreply.github.com> Date: Tue, 12 Sep 2023 17:51:24 -0400 Subject: [PATCH 6/9] Update CHANGELOG.md Co-authored-by: Illia Kovalenko <23364749+illiakovalenko@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca5e7b710f..f44e46a0dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +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) for BYOC ([#1610](https://github.com/Sitecore/jss/pull/1610)) +* `[sitecore-jss-react]` `[sitecore-jss-nextjs]` Component level data fetching(SSR/SSG) for BYOC ([#1610](https://github.com/Sitecore/jss/pull/1610)) ### 🧹 Chores From 4fa09f58ebaccf75a3be4f5f518a717023eaa62c Mon Sep 17 00:00:00 2001 From: Addy Pathania Date: Tue, 12 Sep 2023 19:27:28 -0400 Subject: [PATCH 7/9] remove try catch --- .../src/components/BYOCComponent.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx index aafab2d8a5..ef768059cb 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx @@ -131,7 +131,9 @@ export class BYOCComponent extends React.Component { const ErrorComponent = this.props.errorComponent; - let componentProps: { [key: string]: any } = props.fetchedData; + const isNull = Object.keys(props.fetchedData).length === 0; + + let componentProps: { [key: string]: any } = isNull ? null : props.fetchedData; if (!componentProps) { if (props.params?.ComponentProps) { @@ -168,16 +170,12 @@ export class BYOCComponent extends React.Component { export async function fetchBYOCComponentServerProps( params: BYOCComponentParams ): Promise { - let fetchedData: FEAAS.DataScopes = null; + let fetchedData: FEAAS.DataScopes; const fetchDataOptions: FEAAS.DataOptions = params.ComponentProps ? JSON.parse(params.ComponentProps) : {}; - try { - fetchedData = await FEAAS.DataSettings.fetch(fetchDataOptions || {}); - } catch (e) { - console.error('Fetch BYOC component props failed'); - } + fetchedData = await FEAAS.DataSettings.fetch(fetchDataOptions); return { fetchedData, From a99c5b4f50fde6e6bd7a57623b8cf9b2f38c3ad5 Mon Sep 17 00:00:00 2001 From: Addy Pathania Date: Tue, 12 Sep 2023 19:46:23 -0400 Subject: [PATCH 8/9] fixed failing test --- .../src/components/BYOCComponent.test.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx index 88a2178957..d80beee5f4 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx @@ -12,7 +12,7 @@ describe('BYOCComponent', () => { ComponentName: 'Foo', ComponentProps: JSON.stringify({ prop1: 'value1' }), }, - fetchedData: null, + fetchedData: {}, }; const Foo = () =>

Test

; FEAAS.External.registerComponent(Foo, { @@ -67,7 +67,7 @@ describe('Error handling', () => { ComponentName: 'ExampleComponent', ComponentProps: 'invalid-json', }, - fetchedData: null, + fetchedData: {}, }; const wrapper = mount(); const errorComponent = wrapper.find('DefaultErrorComponent'); @@ -82,7 +82,7 @@ describe('Error handling', () => { ComponentName: 'ExampleComponent', ComponentProps: 'invalid-json', }, - fetchedData: null, + fetchedData: {}, }; const wrapper = mount(); @@ -127,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(); @@ -146,6 +150,7 @@ describe('Error handling', () => { missingComponentComponent: missingComponent, params: { ComponentName: 'NonExistentComponent' }, components: {}, + fetchedData: {}, }; const wrapper = mount(); From 729a470dc7637ab20da5d330410adc2980539364 Mon Sep 17 00:00:00 2001 From: Addy Pathania Date: Tue, 12 Sep 2023 20:02:19 -0400 Subject: [PATCH 9/9] fixed lint error --- packages/sitecore-jss-react/src/components/BYOCComponent.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx index ef768059cb..056d34ed43 100644 --- a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx +++ b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx @@ -170,12 +170,11 @@ export class BYOCComponent extends React.Component { export async function fetchBYOCComponentServerProps( params: BYOCComponentParams ): Promise { - let fetchedData: FEAAS.DataScopes; const fetchDataOptions: FEAAS.DataOptions = params.ComponentProps ? JSON.parse(params.ComponentProps) : {}; - fetchedData = await FEAAS.DataSettings.fetch(fetchDataOptions); + const fetchedData: FEAAS.DataScopes = await FEAAS.DataSettings.fetch(fetchDataOptions || {}); return { fetchedData,