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

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}} />
</>
);
};

Related

Custom component that extend MUI SvgIcon cannot accept `component` prop [duplicate]

Can anyone please explain how Material-UI extends the props of its Button component with the props of my component if I pass a specific component in the component prop?
interface MyLinkProps extends ButtonBaseProps {
someRandomProp: string
}
const MyLink: React.FC<MyLinkProps> = () => {
return <div></div>
}
<Button component={MyLink} someRandomProp="random">Something</Button>
As in this case, the Button component is now aware of the someRandomProp prop that belongs to my component; which is being passed to component prop on the Button component.
I would like to achieve the same effect. I have an Image component which has a prop component which I would like to infer the props of the component that is being passed.
For example, if there is something like:
<MyImage component={NextImage} {...propsOfNextImage} />
Basically, I would like MyImage to auto-detect and extend the props of NextImage.
You can see the type definition of OverridableComponent here, this one is responsible for merging the props of the overridable component with the original props of the component.
For reference, see how it's used in a MUI component here. The first generic type parameter is a type with the following properties:
props: The original props of your component before being merged with the props of the overridable component.
defaultComponent: The default root component if you don't provide any.
import { OverridableComponent } from '#mui/material/OverridableComponent';
interface MyImageProps {
src: string;
myCustomProps?: string;
}
interface MyImageTypeMap {
props: MyImageProps;
defaultComponent: 'img';
}
const MyImage: OverridableComponent<MyImageTypeMap> = (props) => {
const RootComponent = props.component || 'img';
return (
<RootComponent src={props.src} {...props}>
{props.children}
</RootComponent>
);
};
Usage
{/* normal component with MyImageProps. The root component is an img element */}
<MyImage src={src} />
{/* component with MyImageProps & ButtonProps. The root component is a Button*/}
<MyImage src={src} component={Button} variant="contained">
I am actually a button in disguise
</MyImage>
Live Demo

How does Material-UI Button component infer the props for the component passed to `component` prop?

Can anyone please explain how Material-UI extends the props of its Button component with the props of my component if I pass a specific component in the component prop?
interface MyLinkProps extends ButtonBaseProps {
someRandomProp: string
}
const MyLink: React.FC<MyLinkProps> = () => {
return <div></div>
}
<Button component={MyLink} someRandomProp="random">Something</Button>
As in this case, the Button component is now aware of the someRandomProp prop that belongs to my component; which is being passed to component prop on the Button component.
I would like to achieve the same effect. I have an Image component which has a prop component which I would like to infer the props of the component that is being passed.
For example, if there is something like:
<MyImage component={NextImage} {...propsOfNextImage} />
Basically, I would like MyImage to auto-detect and extend the props of NextImage.
You can see the type definition of OverridableComponent here, this one is responsible for merging the props of the overridable component with the original props of the component.
For reference, see how it's used in a MUI component here. The first generic type parameter is a type with the following properties:
props: The original props of your component before being merged with the props of the overridable component.
defaultComponent: The default root component if you don't provide any.
import { OverridableComponent } from '#mui/material/OverridableComponent';
interface MyImageProps {
src: string;
myCustomProps?: string;
}
interface MyImageTypeMap {
props: MyImageProps;
defaultComponent: 'img';
}
const MyImage: OverridableComponent<MyImageTypeMap> = (props) => {
const RootComponent = props.component || 'img';
return (
<RootComponent src={props.src} {...props}>
{props.children}
</RootComponent>
);
};
Usage
{/* normal component with MyImageProps. The root component is an img element */}
<MyImage src={src} />
{/* component with MyImageProps & ButtonProps. The root component is a Button*/}
<MyImage src={src} component={Button} variant="contained">
I am actually a button in disguise
</MyImage>
Live Demo

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

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

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;

React HOC, instanceof, wrapped with?

I use HOC and the compose method from Redux to wrap a component in components. A simple example :
const Textinput = class Textinput extends Component {
...
}
export default compose(
Theme,
Validator,
)(Textinput);
The result rendered by react is :
<theme>
<validator>
<textinput />
Imagine another component with this children :
<Textinput />
<span>Test</span>
<Select />
<br/>
Textinput and Select are wrapped in the Theme/Validator components with HOC. I want to enumerate each element in this component :
const children = React.Children.map(this.props.children, (child) => {
}
But :
how to know if a rendered element is wrapped in a specific component (like Validator or Theme) ?
how to know if the component behind an element is an instanceof Select or Textinput (and not Theme) ?
Another solution is to add a new property on each HOC object like :
Theme.innerInstances = {
instance: WrappedComponent,
innerInstances: WrappedComponent.innerInstances,
};
Validator.innerInstances = {
instance: WrappedComponent,
innerInstances: WrappedComponent.innerInstances,
};

Resources