From 1cdf325d767e27fcad68cc4ef51dba372668e0eb Mon Sep 17 00:00:00 2001
From: illiakovalenko <zlatoborodyi@gmail.com>
Date: Mon, 13 Dec 2021 03:51:59 +0200
Subject: [PATCH 1/5] add `modifyComponentProps` property

---
 .../src/components/Placeholder.tsx            | 23 +++++++++++++++++++
 packages/sitecore-jss-nextjs/src/index.ts     |  2 +-
 .../src/components/PlaceholderCommon.tsx      |  5 +++-
 packages/sitecore-jss-react/src/index.ts      |  2 +-
 4 files changed, 29 insertions(+), 3 deletions(-)
 create mode 100644 packages/sitecore-jss-nextjs/src/components/Placeholder.tsx

diff --git a/packages/sitecore-jss-nextjs/src/components/Placeholder.tsx b/packages/sitecore-jss-nextjs/src/components/Placeholder.tsx
new file mode 100644
index 0000000000..fa51f35e6e
--- /dev/null
+++ b/packages/sitecore-jss-nextjs/src/components/Placeholder.tsx
@@ -0,0 +1,23 @@
+import React, { useContext } from 'react';
+import {
+  Placeholder as ReactPlaceholder,
+  PlaceholderComponentProps,
+} from '@sitecore-jss/sitecore-jss-react';
+import { ComponentPropsReactContext } from './ComponentPropsContext';
+
+export const Placeholder = (props: PlaceholderComponentProps) => {
+  const componentPropsContext = useContext(ComponentPropsReactContext);
+
+  return (
+    <ReactPlaceholder
+      {...props}
+      modifyComponentProps={(initialProps) => {
+        if (!initialProps.rendering.uid) return initialProps;
+
+        const data = componentPropsContext[initialProps.rendering.uid] as { [key: string]: unknown };
+
+        return { ...initialProps, ...data };
+      }}
+    />
+  );
+};
diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts
index 615f0f55a7..e8b49093ae 100644
--- a/packages/sitecore-jss-nextjs/src/index.ts
+++ b/packages/sitecore-jss-nextjs/src/index.ts
@@ -90,10 +90,10 @@ export {
 
 export { Link } from './components/Link';
 export { RichText } from './components/RichText';
+export { Placeholder } from './components/Placeholder';
 
 export {
   ComponentFactory,
-  Placeholder,
   Image,
   ImageField,
   LinkField,
diff --git a/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx b/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx
index b061abb428..19d36c3d45 100644
--- a/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx
+++ b/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx
@@ -42,6 +42,8 @@ export interface PlaceholderProps {
     [name: string]: string;
   };
 
+  modifyComponentProps?: (componentProps: { [key: string]: unknown, rendering: ComponentRendering }) => ({ [key: string]: unknown, rendering: ComponentRendering });
+
   /**
    * A component that is rendered in place of any components that are in this placeholder,
    * but do not have a definition in the componentFactory (i.e. don't have a React implementation)
@@ -85,6 +87,7 @@ export class PlaceholderCommon<T extends PlaceholderProps> extends React.Compone
       PropTypes.object as Requireable<React.ComponentClass<unknown>>,
       PropTypes.func as Requireable<React.FC<unknown>>,
     ]),
+    modifyComponentProps: PropTypes.func,
   };
 
   nodeRefs: Element[];
@@ -191,7 +194,7 @@ export class PlaceholderCommon<T extends PlaceholderProps> extends React.Compone
 
         return React.createElement<{ [attr: string]: unknown }>(
           component as React.ComponentType,
-          finalProps
+          this.props.modifyComponentProps ? this.props.modifyComponentProps(finalProps) : finalProps
         );
       })
       .filter((element) => element); // remove nulls
diff --git a/packages/sitecore-jss-react/src/index.ts b/packages/sitecore-jss-react/src/index.ts
index b2d7993308..5637369f02 100644
--- a/packages/sitecore-jss-react/src/index.ts
+++ b/packages/sitecore-jss-react/src/index.ts
@@ -40,7 +40,7 @@ export {
 } from '@sitecore-jss/sitecore-jss/i18n';
 export { mediaApi } from '@sitecore-jss/sitecore-jss/media';
 export { ComponentFactory } from './components/sharedTypes';
-export { Placeholder } from './components/Placeholder';
+export { Placeholder, PlaceholderComponentProps } from './components/Placeholder';
 export { Image, ImageField } from './components/Image';
 export { RichText, RichTextProps, RichTextPropTypes, RichTextField } from './components/RichText';
 export { Text, TextField } from './components/Text';

From 15e1375ee7daa9d242cf28d8e729e127e0de7936 Mon Sep 17 00:00:00 2001
From: illiakovalenko <zlatoborodyi@gmail.com>
Date: Mon, 13 Dec 2021 13:24:09 +0200
Subject: [PATCH 2/5] Add jsdoc, unit tests

---
 .../src/components/Placeholder.test.tsx       | 41 +++++++++++++++++++
 .../src/components/PlaceholderCommon.tsx      | 14 +++++--
 2 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/packages/sitecore-jss-react/src/components/Placeholder.test.tsx b/packages/sitecore-jss-react/src/components/Placeholder.test.tsx
index 6e942ecff2..ce302b20c5 100644
--- a/packages/sitecore-jss-react/src/components/Placeholder.test.tsx
+++ b/packages/sitecore-jss-react/src/components/Placeholder.test.tsx
@@ -226,6 +226,47 @@ describe('<Placeholder />', () => {
             .indexOf(expectedMessage.value) !== -1
         ).to.be.true;
       });
+
+      it('should apply modifyComponentProps to the final props', () => {
+        const component = dataSet.data.sitecore.route as any;
+        const phKey = 'main';
+        const expectedMessage = (component.placeholders.main as any[]).find((c) => c.componentName)
+          .fields.message;
+
+        const modifyComponentProps = (props) => {
+          if (props.rendering?.componentName === 'DownloadCallout') {
+            return {
+              ...props,
+              extraData: {
+                x: true,
+              },
+            };
+          }
+
+          return props;
+        };
+
+        const renderedComponent = mount(
+          <SitecoreContext componentFactory={componentFactory}>
+            <Placeholder
+              name={phKey}
+              rendering={component}
+              modifyComponentProps={modifyComponentProps}
+            />
+          </SitecoreContext>
+        );
+
+        expect(
+          renderedComponent
+            .find('.download-callout-mock')
+            .html()
+            .indexOf(expectedMessage.value) !== -1
+        ).to.be.true;
+
+        expect(renderedComponent.find('DownloadCallout').prop('extraData')).to.deep.equal({
+          x: true,
+        });
+      });
     });
   });
 
diff --git a/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx b/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx
index 19d36c3d45..6a61c2d296 100644
--- a/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx
+++ b/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx
@@ -16,6 +16,12 @@ type ErrorComponentProps = {
   [prop: string]: unknown;
 };
 
+/** Provided for the component which represents rendering data */
+type ComponentProps = {
+  [key: string]: unknown;
+  rendering: ComponentRendering;
+};
+
 export interface PlaceholderProps {
   [key: string]: unknown;
   /** Name of the placeholder to render. */
@@ -41,9 +47,11 @@ export interface PlaceholderProps {
   params?: {
     [name: string]: string;
   };
-
-  modifyComponentProps?: (componentProps: { [key: string]: unknown, rendering: ComponentRendering }) => ({ [key: string]: unknown, rendering: ComponentRendering });
-
+  /**
+   * Modify final props of component (before render) provided by rendering data.
+   * Can be used in case when you need to insert additional data into the component.
+   */
+  modifyComponentProps?: (componentProps: ComponentProps) => ComponentProps;
   /**
    * A component that is rendered in place of any components that are in this placeholder,
    * but do not have a definition in the componentFactory (i.e. don't have a React implementation)

From d7c4abceaf67c0aa8bafbdf234d40857f3b4b460 Mon Sep 17 00:00:00 2001
From: illiakovalenko <zlatoborodyi@gmail.com>
Date: Mon, 13 Dec 2021 15:33:04 +0200
Subject: [PATCH 3/5] edit jsdoc

---
 .../sitecore-jss-react/src/components/PlaceholderCommon.tsx     | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx b/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx
index 6a61c2d296..2fa357bce7 100644
--- a/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx
+++ b/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx
@@ -50,6 +50,8 @@ export interface PlaceholderProps {
   /**
    * Modify final props of component (before render) provided by rendering data.
    * Can be used in case when you need to insert additional data into the component.
+   * @param {ComponentProps} componentProps component props to be modified
+   * @returns {ComponentProps} modified or initial props
    */
   modifyComponentProps?: (componentProps: ComponentProps) => ComponentProps;
   /**

From 46dfbeb2134cba8c46b65c166f02bc84e30d3403 Mon Sep 17 00:00:00 2001
From: illiakovalenko <zlatoborodyi@gmail.com>
Date: Mon, 13 Dec 2021 15:45:25 +0200
Subject: [PATCH 4/5] fix eslint issue

---
 packages/sitecore-jss-nextjs/src/components/Placeholder.tsx | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/packages/sitecore-jss-nextjs/src/components/Placeholder.tsx b/packages/sitecore-jss-nextjs/src/components/Placeholder.tsx
index fa51f35e6e..42713cb1d1 100644
--- a/packages/sitecore-jss-nextjs/src/components/Placeholder.tsx
+++ b/packages/sitecore-jss-nextjs/src/components/Placeholder.tsx
@@ -14,7 +14,9 @@ export const Placeholder = (props: PlaceholderComponentProps) => {
       modifyComponentProps={(initialProps) => {
         if (!initialProps.rendering.uid) return initialProps;
 
-        const data = componentPropsContext[initialProps.rendering.uid] as { [key: string]: unknown };
+        const data = componentPropsContext[initialProps.rendering.uid] as {
+          [key: string]: unknown;
+        };
 
         return { ...initialProps, ...data };
       }}

From 44ef0f9f4fa4a62b2901f1a3838d10cc2aab4f4a Mon Sep 17 00:00:00 2001
From: illiakovalenko <zlatoborodyi@gmail.com>
Date: Mon, 13 Dec 2021 15:59:24 +0200
Subject: [PATCH 5/5] Edit ConnectedDemo

---
 .../graphql/GraphQL-ConnectedDemo.dynamic.tsx | 35 +++++++++----------
 1 file changed, 17 insertions(+), 18 deletions(-)

diff --git a/samples/nextjs/src/components/graphql/GraphQL-ConnectedDemo.dynamic.tsx b/samples/nextjs/src/components/graphql/GraphQL-ConnectedDemo.dynamic.tsx
index bdea9ed7cb..3ab426a1cb 100644
--- a/samples/nextjs/src/components/graphql/GraphQL-ConnectedDemo.dynamic.tsx
+++ b/samples/nextjs/src/components/graphql/GraphQL-ConnectedDemo.dynamic.tsx
@@ -4,7 +4,6 @@ import {
   Link,
   GetServerSideComponentProps,
   GetStaticComponentProps,
-  useComponentProps,
   constants,
   GraphQLRequestClient,
   withDatasourceCheck,
@@ -27,9 +26,9 @@ type GraphQLConnectedDemoData = {
   contextItem: RouteItem;
 };
 
-const GraphQLConnectedDemo = (props: StyleguideComponentProps): JSX.Element => {
-  const data = useComponentProps<GraphQLConnectedDemoData>(props.rendering.uid);
+type GraphQLConnectedDemoProps = StyleguideComponentProps & GraphQLConnectedDemoData;
 
+const GraphQLConnectedDemo = (props: GraphQLConnectedDemoProps): JSX.Element => {
   useEffect(() => {
     resetEditorChromes();
   }, []);
@@ -46,41 +45,41 @@ const GraphQLConnectedDemo = (props: StyleguideComponentProps): JSX.Element => {
         <code>getServerSideProps</code> execution.
       </p>
 
-      {data && data.datasource && (
+      {props.datasource && (
         <div>
           <h4>Datasource Item (via Connected GraphQL)</h4>
-          id: {data.datasource.id}
+          id: {props.datasource.id}
           <br />
-          name: {data.datasource.name}
+          name: {props.datasource.name}
           <br />
-          sample1: {data.datasource.sample1?.value}
+          sample1: {props.datasource.sample1?.value}
           <br />
-          sample1 (editable): <Text field={data.datasource.sample1?.jsonValue} />
+          sample1 (editable): <Text field={props.datasource.sample1?.jsonValue} />
           <br />
           sample2:
           <br />
           <ul>
-            <li>text: {data.datasource.sample2?.text}</li>
-            <li>url: {data.datasource.sample2?.url}</li>
-            <li>target: {data.datasource.sample2?.target}</li>
+            <li>text: {props.datasource.sample2?.text}</li>
+            <li>url: {props.datasource.sample2?.url}</li>
+            <li>target: {props.datasource.sample2?.target}</li>
             <li>
-              editable: <Link field={data.datasource.sample2?.jsonValue} />
+              editable: <Link field={props.datasource.sample2?.jsonValue} />
             </li>
-            <li>field type: {data.datasource.sample2?.definition?.type}</li>
-            <li>field is shared?: {data.datasource.sample2?.definition?.shared.toString()}</li>
+            <li>field type: {props.datasource.sample2?.definition?.type}</li>
+            <li>field is shared?: {props.datasource.sample2?.definition?.shared.toString()}</li>
           </ul>
         </div>
       )}
-      {data && data.contextItem && (
+      {props.contextItem && (
         <div>
           <h4>Route Item (via Connected GraphQL)</h4>
-          id: {data.contextItem.id}
+          id: {props.contextItem.id}
           <br />
-          page title: {data.contextItem.pageTitle?.value}
+          page title: {props.contextItem.pageTitle?.value}
           <br />
           children:
           <ul>
-            {data.contextItem.children.results.map((child) => {
+            {props.contextItem.children.results.map((child) => {
               const routeItem = child as RouteItem;
 
               return (