Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[packages/sitecore-jss-react] Combine fetched and datasource item props in BYOC and FEAAS #1694

Merged
merged 6 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/sitecore-jss-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"url": "https://github.com/sitecore/jss/issues"
},
"devDependencies": {
"@sitecore-feaas/clientside": "^0.5.5",
"@sitecore-feaas/clientside": "^0.5.6",
"@types/chai": "^4.3.4",
"@types/chai-string": "^1.4.2",
"@types/deep-equal": "^1.0.1",
Expand Down Expand Up @@ -58,7 +58,7 @@
"typescript": "~4.9.3"
},
"peerDependencies": {
"@sitecore-feaas/clientside": "^0.5.5",
"@sitecore-feaas/clientside": "^0.5.6",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,99 @@ describe('BYOCComponent', () => {
expect(fooComponent.find('#foo-content')).to.have.length(1);
});

it('should use datasource fields when provided', () => {
const fields = {
prop1: {
value: 'value2',
},
};
const mockProps = {
params: {
ComponentName: 'Foo',
},
fetchedData: {},
fields,
};
const Foo = () => <p id="foo-content">Test</p>;
FEAAS.External.registerComponent(Foo, {
name: 'Foo',
properties: {
prop1: {
type: 'string',
},
},
});
const wrapper = mount(<BYOCComponent {...mockProps} />);
const fooComponent = wrapper.find('feaas-external');
expect(fooComponent).to.have.lengthOf(1);
expect(fooComponent.prop('prop1')).to.equal('value2');
expect(fooComponent.prop('data-external-id')).to.equal('Foo');
expect(fooComponent.find('#foo-content')).to.have.length(1);
});

it('should prefer ComponentProps over datasource fields', () => {
const fields = {
prop1: {
value: 'value2',
},
};
const mockProps = {
params: {
ComponentName: 'Foo',
ComponentProps: JSON.stringify({ prop1: 'value1' }),
},
fetchedData: {},
fields,
};
const Foo = () => <p id="foo-content">Test</p>;
FEAAS.External.registerComponent(Foo, {
name: 'Foo',
properties: {
prop1: {
type: 'string',
},
},
});
const wrapper = mount(<BYOCComponent {...mockProps} />);
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 combine ComponentProps and datasource fields', () => {
const fields = {
prop2: {
value: 'value2',
},
};
const mockProps = {
params: {
ComponentName: 'Foo',
ComponentProps: JSON.stringify({ prop1: 'value1' }),
},
fetchedData: {},
fields,
};
const Foo = () => <p id="foo-content">Test</p>;
FEAAS.External.registerComponent(Foo, {
name: 'Foo',
properties: {
prop1: {
type: 'string',
},
},
});
const wrapper = mount(<BYOCComponent {...mockProps} />);
const fooComponent = wrapper.find('feaas-external');
expect(fooComponent).to.have.lengthOf(1);
expect(fooComponent.prop('prop1')).to.equal('value1');
expect(fooComponent.prop('prop2')).to.equal('value2');
expect(fooComponent.prop('data-external-id')).to.equal('Foo');
expect(fooComponent.find('#foo-content')).to.have.length(1);
});

it('should render with static and fetched props when props are prefetched', () => {
const fetchedData = {
prop2: 'prefetched_value1',
Expand Down Expand Up @@ -58,7 +151,7 @@ describe('BYOCComponent', () => {
const fooComponent = wrapper.find('feaas-external');
expect(fooComponent).to.have.lengthOf(1);
expect(fooComponent.prop('prop1')).to.equal('value1');
expect(fooComponent.prop('datasources')).to.equal('{"prop2":"prefetched_value1"}');
expect(fooComponent.prop('datasources')).to.equal('{"prop2":"prefetched_value1","_":{}}');
expect(fooComponent.prop('data-external-id')).to.equal('Foo');
expect(fooComponent.find('#foo-content')).to.have.length(1);
});
Expand Down
8 changes: 4 additions & 4 deletions packages/sitecore-jss-react/src/components/BYOCComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export class BYOCComponent extends React.Component<BYOCComponentProps> {

const ErrorComponent = this.props.errorComponent;

let componentProps: { [key: string]: any } = null;
let componentProps: { [key: string]: any } = {};

if (props.params?.ComponentProps) {
try {
Expand All @@ -150,16 +150,16 @@ export class BYOCComponent extends React.Component<BYOCComponentProps> {
<DefaultErrorComponent error={e as Error} />
);
}
} else {
componentProps = props.fields ? getDataFromFields(props.fields) : {};
}
// apply props from item datasource
const dataSourcesData = { ...props.fetchedData, _: getDataFromFields(props.fields ?? {}) };

// we render fallback on client to avoid problems with client-only components
return (
<FEAAS.ExternalComponent
componentName={componentName}
clientFallback={fallbackComponent}
datasources={props.fetchedData}
datasources={dataSourcesData}
{...componentProps}
/>
);
Expand Down
56 changes: 17 additions & 39 deletions packages/sitecore-jss-react/src/components/FEaaSComponent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,12 @@ describe('<FEaaSComponent />', () => {
});

describe('data', () => {
it('should send override data', () => {
it('should send fetched data', () => {
const props: FEaaSComponentProps = {
params: {
...requiredParams,
ComponentDataOverride: '{ "foo": "bar", "baz": 1 }',
},
fetchedData: undefined,
fetchedData: { foo: 'bar', baz: 1 },
template: '<h1 data-path="foo"></h1><h2 data-path="baz"></h2>',
};
const wrapper = shallow(<FEaaSComponent {...props} />);
Expand Down Expand Up @@ -123,10 +122,10 @@ describe('<FEaaSComponent />', () => {
},
};
const template = `
<h1 data-path="sampleText"></h1>
<img data-path-src="sampleImage.src" data-path-alt="sampleImage.alt"></img>
<p data-path="sampleNumber"></p>
<a data-path-href="sampleLink.href" data-path-id="sampleLink.id"></a>`;
<h1 data-path="xm.sampleText"></h1>
<img data-path-src="xm.sampleImage.src" data-path-alt="xm.sampleImage.alt"></img>
<p data-path="xm.sampleNumber"></p>
<a data-path-href="xm.sampleLink.href" data-path-id="xm.sampleLink.id"></a>`;
const props: FEaaSComponentProps = {
params: {
...requiredParams,
Expand All @@ -137,58 +136,37 @@ describe('<FEaaSComponent />', () => {
const wrapper = shallow(<FEaaSComponent {...props} />);
expect(wrapper).to.have.length(1);
const output = wrapper.html();
expect(output).to.contain(`<h1 data-path="sampleText">${fields.sampleText.value}</h1>`);
expect(output).to.contain(`<h1 data-path="xm.sampleText">${fields.sampleText.value}</h1>`);
expect(output).to.contain(
`<img data-path-src="sampleImage.src" data-path-alt="sampleImage.alt" src="${fields.sampleImage.value.src}" alt="${fields.sampleImage.value.alt}"/>`
`<img data-path-src="xm.sampleImage.src" data-path-alt="xm.sampleImage.alt" src="${fields.sampleImage.value.src}" alt="${fields.sampleImage.value.alt}"/>`
);
expect(output).to.contain(`<p data-path="sampleNumber">${fields.sampleNumber.value}</p>`);
expect(output).to.contain(`<p data-path="xm.sampleNumber">${fields.sampleNumber.value}</p>`);
expect(output).to.contain(
`<a data-path-href="sampleLink.href" data-path-id="sampleLink.id" href="${fields.sampleLink.value.href}" id="${fields.sampleLink.value.id}"></a>`
`<a data-path-href="xm.sampleLink.href" data-path-id="xm.sampleLink.id" href="${fields.sampleLink.value.href}" id="${fields.sampleLink.value.id}"></a>`
);
});

it('should prefer override data over datasource fields', () => {
it('should combine fetched data with datasource fields', () => {
const fields: ComponentFields = {
sampleText: {
fieldText: {
value: 'Welcome to Sitecore JSS',
},
};
const override = JSON.stringify({ sampleText: { value: 'Welcome to FEAAS' } });
const fetched = { customDatasourceId: { fetchedText: 'Welcome to FEAAS' } };
const props: FEaaSComponentProps = {
params: {
...requiredParams,
ComponentDataOverride: override,
},
fetchedData: fetched,
fields,
template: '<h1 data-path="sampleText.value"></h1>',
template:
'<h1 data-path="xm.fieldText"></h1><h1 data-path="customDatasourceId.fetchedText"></h1>',
};

const wrapper = shallow(<FEaaSComponent {...props} />);
expect(wrapper).to.have.length(1);
expect(wrapper.html()).to.contain('Welcome to FEAAS');
});

it('should send prefetched data', () => {
const fetchedData = {
foo: 'bar',
baz: 42,
};

const props: FEaaSComponentProps = {
params: {
...requiredParams,
ComponentDataOverride: '{ "foo": "test", "baz": 22 }',
},
fetchedData,
template: '<h1 data-path="foo"></h1> <h2 data-path="baz"></h2>',
};

const wrapper = shallow(<FEaaSComponent {...props} />);

expect(wrapper).to.have.length(1);
const output = wrapper.html();
expect(output).to.contain(`<h1 data-path=\"foo\">${fetchedData.foo}</h1>`);
expect(output).to.contain(`<h2 data-path=\"baz\">${fetchedData.baz}</h2>`);
expect(wrapper.html()).to.contain('Welcome to Sitecore JSS');
});
});
});
21 changes: 4 additions & 17 deletions packages/sitecore-jss-react/src/components/FEaaSComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,29 +75,16 @@ export const FEaaSComponent = (props: FEaaSComponentProps): JSX.Element => {
// Missing FEaaS component required props
return null;
}

let data: { [key: string]: unknown } = null;
if (props.fetchedData === null || props.fetchedData === undefined) {
if (props.params?.ComponentDataOverride) {
// Use override data if provided
try {
data = JSON.parse(props.params.ComponentDataOverride);
} catch (e) {
data = null;
}
} else if (props.fields) {
// Otherwise use datasource data (provided in fields)
data = getDataFromFields(props.fields);
}
}
// combine fetchedData from server with datasource data (if present)
const data = { ...props.fetchedData, _: getDataFromFields(props.fields ?? {}) };

// FEaaS control would still be hydrated by client
// we pass all the props as a workaround to avoid hydration error, until we convert all JSS components to server side
// this also allows component to fall back to full client-side rendering when template or src is empty
// FEAAS should not fetch anything, since JSS does the fetching - so we pass empty array into fetch param
return (
<FEAAS.Component
data={props.fetchedData || data}
data={data}
template={props.template}
cdn={props.params?.ComponentHostName}
library={props.params?.LibraryId}
Expand Down Expand Up @@ -126,7 +113,7 @@ export async function fetchFEaaSComponentServerProps(
pageState && pageState !== LayoutServicePageState.Normal ? 'staged' : 'published';
const src = endpointOverride || composeComponentEndpoint(params, revisionFallback);
let template = '';
let fetchedData: FEAAS.DataScopes = null;
let fetchedData: FEAAS.DataScopes = {};
const fetchDataOptions: FEAAS.DataOptions = params.ComponentDataOverride
? JSON.parse(params.ComponentDataOverride)
: {};
Expand Down
12 changes: 6 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6806,14 +6806,14 @@ __metadata:
languageName: node
linkType: hard

"@sitecore-feaas/clientside@npm:^0.5.5":
version: 0.5.5
resolution: "@sitecore-feaas/clientside@npm:0.5.5"
"@sitecore-feaas/clientside@npm:^0.5.6":
version: 0.5.6
resolution: "@sitecore-feaas/clientside@npm:0.5.6"
dependencies:
"@sitecore/byoc": ^0.2.5
peerDependencies:
react-dom: ">=16.8.0"
checksum: 108ed61488f7f5777d3ae846f6d1c188a245440a511b7bc06200f4b236cf5e140075646efa4e3a1070ce7737fb4a2a808383e65d6570bd7c6a540dc183cc6e29
checksum: 6346d7cac5dc295ddf377fd802404229256fdfb4e2cabdf0cbd0ae8350a2fafe7e2b49abe31025a9dec848f4545e96178ec2173369a328799845ed6253c4cb90
languageName: node
linkType: hard

Expand Down Expand Up @@ -7143,7 +7143,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@sitecore-jss/sitecore-jss-react@workspace:packages/sitecore-jss-react"
dependencies:
"@sitecore-feaas/clientside": ^0.5.5
"@sitecore-feaas/clientside": ^0.5.6
"@sitecore-jss/sitecore-jss": 21.7.0-canary.33
"@types/chai": ^4.3.4
"@types/chai-string": ^1.4.2
Expand Down Expand Up @@ -7177,7 +7177,7 @@ __metadata:
ts-node: ^10.9.1
typescript: ~4.9.3
peerDependencies:
"@sitecore-feaas/clientside": ^0.5.5
"@sitecore-feaas/clientside": ^0.5.6
react: ^18.2.0
react-dom: ^18.2.0
languageName: unknown
Expand Down