To accomplish this, I recommend you use the cloneElement method of React.
Here is the desired logic that uses the cloneElement method inside the CallToAction component:
import React, { cloneElement } from "react";
export default function CallToAction({ children, ...ctaProps }) {
function cloneWithProps(element, key) {
return cloneElement(element, { ...ctaProps, ...element.props, key });
}
let childrenWithProps;
if (children.length) {
childrenWithProps = children.map((child, index) =>
cloneWithProps(child, index)
);
} else {
childrenWithProps = cloneWithProps(children);
}
return <div>{childrenWithProps}</div>;
}
The cloneElement method accepts three parameters, but in your case, the first two are enough.
cloneElement(element, props)
element is a react element
props is an object that overrides the element’s props.
You can use the spread operator to add parent element’s props to the child. Also, don’t forget to add the key prop to the child when you use multiple children to prevent the “unique key” error.
I have prepared a project that demonstrates the usage of React’s cloneElement method. You can check it out below:
This is what Context is designed to be used for, though I would think very carefully about whether you really need to do this or whether you can just pass directly via props. If there’s only one level – eg in your example there you’ve got a CTA component with two direct children only it looks like – then why not just pass in stuff via props directly? So like a CTATitle and a CTAButton specifically for that purpose. Otherwise, regardless of how you do it, you’re adding a lot more complexity Vs. just writing a few more components.
import { forwardRef, useRef, createContext, useContext } from "react";
// Create the context
const CallToActionContext = createContext("SOME_LAYOUT");
// Add the context provider into the top level component:
const CallToAction = forwardRef<HTMLDivElement>((props, ref) => {
const { children, ...rest } = props;
return (
<div>
<CallToActionContext.Provider value={props.layout}>
{children}
</CallToActionContext.Provider>
</div>
);
});
// Can now directly access the value from children without needing
// to know what the children are, how many there are, what the structure is etc:
const SomeChildComponent = () => {
const layout = useContext(CallToActionContext);
return <p>This is the value for layout: {layout}</p>;
}