How to pass data down to a component property in React? - reactjs

Many React components allow passing custom components as props, in order to customize certain parts of the UI. A popular example would be react-select, which allows to specify custom replacements for all its individual sub components.
In this example, I'm using a modal dialog component that allows specifying custom footer component in its components prop:
const Footer = () => <button>Close</button>;
const MyModal = ({ onClose, closeLabel }) => <Modal components={{ footer: Footer }}/>;
I would like to pass certain data down to the custom component. In this particular example, I would like to pass down the onClose and closeLabel props to the Footer component. What I could do is to declare the Footer component inline:
const MyModal = ({ onClose, closeLabel }) => (
<Modal
components={{ footer: () => <button onClick={onClose}>{closeLabel}</button> }}
/>
);
The problem with this approach is that every time MyModal is rendered, a new footer component is created, which causes React to completely recreate the footer DOM. In this simple example that wouldn't be a big problem, but in more complex scenarios it would make things slow and also cause the component to lose its state. Wrapping the footer component in useCallback() would partially solve the problem, but only as long as none of the values passed down into the footer component change.
I could use context to pass down the value, but that seems like a really complicated solution for a really simple problem. Are there any alternatives?

you can do something similar like this:
export default function App() {
return (
<MyModal>
<Footer /> // this will get the new props
</MyModal>
);
}
const Footer = ({ onClose, closeLabel }) => (
<button onClick={onClose}>{closeLabel}</button>
);
const MyModal = ({
onClose,
closeLabel,
children // here any child you pass to this component will have both onClose and closeLabel as props
}) => {
return (
<div>
{React.Children.map(children, (child) => {
return React.cloneElement(child, {
onClose,
closeLabel
});
})}
</div>
);
};
but anyways there is something not clear there in your code, if you are getting the onClose and closeLabel as props inside MyModal why just don't pass them to the Footer from where are you getting them? and if you don't have control over Modal component then you can't do anything else than giving it inline

Related

how to pass props to component if the given argument is in </> syntax?

I am making a library for show/hide modals and it works as follows,
you make a wrapper around your code:
<ModalProvider>
// all of your app code here
</ModalProvider>
then you can use the built-in use context useModal() hook to show and to hide the modal as follows:
const ContactPage = () => {
const { showModal } = useModal()
return (
<button
onClick={() => showModal(<MyModal prop1="a" prop2="b" prop3="c" />)}
>
Show Modal!
</button>
)
}
Now the consumer of my library have passed the argument <MyModal .../> to my showModal() function, I want to pass the following extra props to his MyModal component, so he can use them inside his MyModal component code:
onClose (only this one)
But I don't know how?
I have tried to read the component props and to set them in my showModal method as:
const showModal = (modal) => {
modal.props.onClose = () => console.log('modal closed!')
}
but react refused by yelling at me saying:
How can I solve this problem?

Is there a way to bubble up a ref for a nested component rendered by a function?

I'm using React JS and Stripe JS to render a reusable payment form.
This is working fine except for one case which requires a button to be rendered outside of the component.
I was hoping to use a ref for the custom payment form but, because of the way Stripe requires the component to be injected and wrapped with an ElementsConsumer component, the underlying component class that is rendered isn't accessible so the 'ref' property cannot be used.
In the below example, would there be a way for me to allow a ref to be used for the '_PaymentForm' class when the 'PaymentForm' function is used to render it?
Thanks!
class _PaymentForm extends React.Component<PaymentFormProps, PaymentFormState> {
...
}
const InjectedPaymentForm = (props: PaymentFormProps) => {
return (
<ElementsConsumer>
{({ elements, stripe }) => (
<_PaymentForm {...props} elements={elements} stripe={stripe} />
)}
</ElementsConsumer>
);
}
export const PaymentForm = (props: PaymentFormProps) => {
const stripePromise = loadStripe(props.stripeApiKey);
return (
<Elements stripe={stripePromise}>
<InjectedPaymentForm {...props} />
</Elements>
);
}
export default PaymentForm;

Can two React distinct and mounted components communicate with each other using props?

A simple example for explaining my question:
ButtonComponent is a component mounted on <div id="btn-container"> element
ModalComponent is a component mounted on <div id="modal-container"> element
So two components mounted on different elements in different places in the page, not sharing a common React element.
ModalComponent receive a prop named isOpen to trigger its visibility:
<ModalComponent isOpened={isOpened} />
The ButtonComponent should, in some way, pass the prop isOpen to the ModalComponent without sharing the same "root" component. Is this even possible?
Of course the "normal way" to do this would be a ButtonPlusModalComponent like the following:
export const ButtonPlusModalComponent = () => {
const [isOpened, setIsOpened] = useState(false);
return (
<>
<ButtonComponent onClick={() => setIsOpened(true)} />
<ModalComponent isOpened={isOpened} />
</>
);
};
I'm using React with a regular PHP application, so... not a complete "full" React application, only some parts and portions of the page are React components, no router at all.
So, I have that button somewhere in the page. That button should open the modal component, which is a... React component placed elsewhere in the page.
EDIT: explain why the question.
You must both component to share a common parent, thats how React works as Data Flows Down.
If you can, couple them into the same tree and use common solutions like Context API.
But, you describing a situation where the components are decoupled, so in order to have a common parent and mount a component into another HTML element, you need to use React.Portal.
Therefore you need one component to mount the other using Portals:
<body>
<div id="modal-container"></div>
<div id="btn-container"></div>
</body>
const Modal = ({ open }) => open && <>Modal</>;
const App = () => {
const [open, toggleOpen] = useReducer((p) => !p, true);
return (
<>
<button onClick={toggleOpen}>toggle</button>
{ReactDOM.createPortal(
<Modal open={open} />,
document.getElementById("btn-container")
)}
</>
);
};
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("modal-container")
);

React, pass component as a child and then render it with new prop

I want to create a modal wrapper. where I reuse the bootstrap modal and make it a component that receives a component "x" as a child and when I render component "x" I can send a prop like handleclosemodal() to the component "x".
Im doing something like this, sending the component as a prop:
<Modal component={<ComponentForm onSubmit={addComment} />}/>
Which works fine, when I render it in the Modal like this:
const Modal = (props) => {
const handleclosemodal = () => {
codeToCloseModalHere...
}
return (
<div>
{props.component}
</div>
)
}
But I need I need to send handleclosemodal in props.component in the Modal and I don't know how to do that.
I found out a way to do it. Using cloneElement in the Wrapper
{React.cloneElement(this.props.component,
{propFromWrapper:Variable/function})}

Spreading a component's props across two different child components in React

I am trying to create a custom email input component (for a form) that wraps a Material-UI TextField component inside a custom gridding component that I made. Ideally, I would like to be able to pass any TextField prop I want into this component and have it applied to the inner TextField component by spreading the props, but I also would like to be able to pass any props for the custom gridding component and apply them to the grid component also via spreading.
Example (where variant is a TextField prop and width is a CustomGrid prop):
// CustomEmailField.tsx
...
export const CustomEmailField: React.FC<TextFieldProps & CustomGridProps> = (props) => {
return(
<CustomGrid {...props as CustomGridProps}>
<TextField {...props as TextFieldProps} />
</CustomGrid>
);
};
// index.tsx
...
const App = () => {
return(
<>
<h1>Enter your email</h1>
<CustomEmailField variant={'outlined'} width={2} />
</>
);
};
However, when spreading the props for the gridding component, I get an error message saying that the TextField props (variant in this example) do not exist for this gridding component, and likewise that the gridding component's props (width in this example) don't exist for the TextField component.
What would be a good way to solve this issue so that I can still have flexibility over the props I pass in to each (child) component without having to hardcode what props can be accepted by the email (parent) component?
Just create a new props type.
export type CustomEmailFieldProps = {
textField: TextFieldProps;
customGrid: CustomGridProps;
}
export const CustomEmailField: React.FC<CustomEmailFieldProps> = ({textField, customGrid}) => {
return(
<CustomGrid {...customGrid}>
<TextField {...textField} />
</CustomGrid>
);
};
To use just create an object of the props you want to pass to each.
// index.tsx
...
const App = () => {
return(
<>
<h1>Enter your email</h1>
<CustomEmailField textField={{variant: 'outlined'}} customGrid={{width: 2}} />
</>
);
};

Resources