I want the users of my component to be able to pass in an object of components, and I want to allow them to use functional or class based components.
type InputComponents = Record<string, React.ComponentType>
This allows either FC or class components but I didn't find definitive guidelines on how to render them.
const inputComponents = {
a: ({ placeholder }) => ( // Functional component
<input
type="text"
value={state.a}
placeholder={placeholder}
onChange={(e) => {
const value = e.currentTarget.value;
setState((s) => ({ ...s, a: value }));
}}
/>
),
b: InputClass // Class component
};
Both of the components render fine without Typescript complaining like this:
const A = inputComponents.a;
const B = inputComponents.b;
<A placeholder="Type something" />
<B placeholder="Type something" />
But this is actually misleading - The functional one (a) will loose focus every key press.
The only way to make the functional component not loose focus every key press is like this:
inputComponents.a({ placeholder: 'Type something' })
Typescript doesn't even consider that a valid way to render a component and yet it's the only fully working way..... it errors with "This expression is not callable." and also fails for the Class component so I have to do this:
// Render as JSX for class components and call as a function for FC ones...
Component.prototype.isReactComponent ? <Component placeholder={x} /> : Component({ placeholder: x })
You can see the problem in action here:
function SomeComponent({ inputComponents }) {
const B = inputComponents.b;
const C = inputComponents.c;
return (
<div className="SomeComponent">
<p>This FC component doesn't loose focus:</p>
{inputComponents.a({ placeholder: 'Type something' })}
<p>This one does:</p>
<B placeholder="Type something" />
<p>Rendering a class component as JSX works though:</p>
<C placeholder="Type something" />
</div>
);
}
class InputClass extends React.Component {
state = {
value: ''
};
render() {
return (
<input
type="text"
value={this.state.value}
placeholder={this.props.placeholder}
onChange={(e) => {
this.setState({ value: e.currentTarget.value });
}}
/>
);
}
}
function App() {
const [state, setState] = React.useState({
a: '',
b: ''
});
const inputComponents = {
a: ({ placeholder }) => (
<input
type="text"
value={state.a}
placeholder={placeholder}
onChange={(e) => {
const value = e.currentTarget.value;
setState((s) => ({ ...s, a: value }));
}}
/>
),
b: ({ placeholder }) => (
<input
type="text"
value={state.b}
placeholder={placeholder}
onChange={(e) => {
const value = e.currentTarget.value;
setState((s) => ({ ...s, b: value }));
}}
/>
),
c: InputClass
};
return (
<div className="App">
<SomeComponent inputComponents={inputComponents} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Is React broken? Or what is the correct way to handle this without relying on hacking TS errors and using internal stuff like isReactComponent? Thanks