-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Question] Conditional Rendering with forwardRef #167
Comments
hey! sorry for slow response, been busy for a bit. I'll have a look but frankly havent needed to use forwardRef much 😅 i think you got forwardRef working |
No worries. I got it working - yes, but I have to use a workaround with |
Because of how higher order functions work with functions that have overload signatures, there's not really any easy way of doing this. My best recommendation for the happy middle is to have a union of your different variants as your props for the render function, and to type the output manually: import * as React from 'react'
interface ButtonProps extends Omit<JSX.IntrinsicElements['button'], 'ref'> {
href?: undefined
}
interface AnchorProps extends Omit<JSX.IntrinsicElements['a'], 'ref'> {
href: string // this should actually be required for TS to properly discriminate the two
}
type PolymorphicProps = ButtonProps | AnchorProps
type PolymorphicButton = {
(props: AnchorProps): JSX.Element
(props: ButtonProps): JSX.Element
}
const Button: PolymorphicButton = React.forwardRef(
(props: PolymorphicProps, ref: any) => {
return props.href != null
? <a {...props} ref={ref} />
: <button {...props} ref={ref} />
}
) as any
// e is inferred as MouseEvent<HTMLAnchorElement>
const AnchorButton = <Button href='abc' onClick={e => {}} />
// e is inferred as MouseEvent<HTMLButtonElement>
const ButtonButton = <Button onClick={e => {}} /> Playground link. |
This example actually gives an error with TS 4.1.3 when you pass ref to the component: |
havent had a chance to look but fix welcome |
So I've tried to hack some things, seems like it works, but maybe there is a better way to type it? Code just in case: import * as React from 'react';
type ButtonProps = JSX.IntrinsicElements['button'] & {
href?: undefined;
}
type AnchorProps = JSX.IntrinsicElements['a'] & {
href: string;
}
// Does not work for some reason
interface ButtonProps2 extends React.ButtonHTMLAttributes<HTMLButtonElement> {
// Does not work, can't pass ref
// interface ButtonProps2 extends Omit<JSX.IntrinsicElements['button'], 'ref'> {
href?: undefined;
}
// Does not work for some reason
interface AnchorProps2 extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
// Does not work, can't pass ref
// interface AnchorProps2 extends Omit<JSX.IntrinsicElements['a'], 'ref'> {
href: string;
}
type PolymorphicProps = ButtonProps | AnchorProps;
type PolymorphicButton = {
(props: AnchorProps): JSX.Element;
(props: ButtonProps): JSX.Element;
};
const isAnchor = (props: PolymorphicProps): props is AnchorProps => {
return props.href != undefined;
};
export const Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, PolymorphicProps>(
(props, ref) => {
return isAnchor(props) ? (
<a
{...props}
ref={ref as React.ForwardedRef<HTMLAnchorElement>}
/>
) : (
<button
{...props}
ref={ref as React.ForwardedRef<HTMLButtonElement>}
/>
);
},
) as PolymorphicButton;
const refButton = React.createRef<HTMLButtonElement>();
const refAnchor = React.createRef<HTMLAnchorElement>();
// Need to use null as default, error otherwise
const useRefButton = React.useRef<HTMLButtonElement>(null)
const useRefAnchor = React.useRef<HTMLAnchorElement>(null)
const tests = <>
// All fine with createRef, event inferred
<Button ref={refButton} className="class" onClick={e => console.log(e)} />
<Button ref={refAnchor} className="class" href="someHref" onClick={e => console.log(e)} />
// All fine with useRef, event inferred
<Button ref={useRefButton} className="class" onClick={e => console.log(e)} />
<Button ref={useRefAnchor} className="class" href="someHref" onClick={e => console.log(e)} />
// Button can be disabled
<Button ref={useRefButton} disabled className="class" onClick={e => console.log(e)} />
// Anchor cannot
<Button ref={useRefAnchor} disabled className="class" href="someHref" onClick={e => console.log(e)} />
// Not valid type for button
<Button ref={refButton} type="wow" onClick={e => console.log(e)} />
// Ok now with valid type="submit"
<Button ref={refButton} type="submit" onClick={e => console.log(e)} />
// Anchor can have some type too, it's valid
<Button ref={refAnchor} href="someHref" type="submit" onClick={e => console.log(e)} />
<Button ref={refAnchor} href="someHref" type="nonButtonType" onClick={e => console.log(e)} />
// Anchor ref is not valid for button
<Button ref={refAnchor} onClick={e => console.log(e)} />
<Button ref={useRefAnchor} onClick={e => console.log(e)} />
// Button ref is not valid for anchor
<Button ref={refButton} href="someHref" onClick={e => console.log(e)} />
<Button ref={useRefButton} href="someHref" onClick={e => console.log(e)} />
</> |
great submission! im not spending any time to validate this right now, but i'll update the docs if anyone else does. thank you! |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions! |
Hi there!
Awesome cheat-sheet, it helped me a lot! Thanks for that.
However, I'm facing an issue atm where a simple type overload isn't working for me, or I just dont know how to type it when adding a simple
forwardRef
infront of the conditional component.to the playground!
Is there any way to add a type overload for the
forwardRef
function here?EDIT:
I got it working with a workaround! (finally omg)
link
I would still loooooove to get it working with
React.forwardRef
though!The text was updated successfully, but these errors were encountered: