React HOC, instanceof, wrapped with? - reactjs

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,
};

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

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

React passing additional classNames to child component in addition to other props

I'd like to pass additional classNames into a child component, and also pass down any other props.
For example:
class Parent extends Component {
render() {
<Child
className="parent-class"
flavor="chocolate"
/>
}
}
class Child extends Component {
render() {
<div className="child-class" {...props}>
</div>
}
}
In this case, I would like the Child component div to have the "flavor" prop, and also have both classes "parent-class" and "child-class". However as it is, the className="child-class" will be overwritten by {...props}.
The only workaround I can think of is putting the {...props} before the className in the Child component:
<div {...props} className={`child-class ${props.className}`}>
Is this the only workaround? Or is there a cleaner solution?
I typically use the classnames package and the rest operator for things like this.
import classNames from 'classnames';
class Parent extends Component {
render() {
<Child
className="parent-class"
flavor="chocolate"
/>
}
}
class Child extends Component {
render() {
const { className, ...rest } = this.props;
const childClassNames = classNames('child-class', className);
return (
<div className={childClassNames} {...rest}>
</div>
);
}
}
You can call rest whatever you like, e.g. ...props will create an object variable called props which would contain everything from this.props except for className.
Also, the classnames package is very widely used, and lets you do other cool things like conditionally include class names.
If you are using functional components, the approach is almost identical to Bryan Downings answer. For simplicities sake I am only posting the implemented child component.
import { Tabs as AntdTabs } from 'antd'
/**
* simplified exmaple, based on our custom Antd Tabs*
* AntD Tabs with custom styling pre-applied.
* #param props The default TabsProps.
* #returns Antd Tabs.
*/
const Tabs = (props: TabsProps) => {
const {
className,
...rest
} = props
const cls = classnames(className, 'custom-tabs')
return (
<>
<AntdTabs
className={cls}
{...rest} >
</AntdTabs>
</>
)
}
export { Tabs }

How do I get the wrapped component from ProxyComponent?

When using material-ui(^1.0.0-beta.24) and adding JSS Styles to my component as such:
class Counter extends Component{
getCount= () => {
return this.state.count;
}
}
export default withStyles(stylesJss)(Counter);
Accessing the counter component via the "ref" prop in the parent component like:
<Counter ref={(ref) => this.counter = ref} />
this.counter results in a ProxyComponent object instead of the underlying Counter class due to the withStyles wrapper. I would like to access the Counter class and it's method(s) like such: this.counter.getCount() from the parent and use it as a standard React uncontrolled component. How can this be obtained?
You can access the wrapped components ref via the innerRef property
<Counter innerRef={(ref) => this.counter = ref} />

Resources