Skip to content
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

Closed
mrtnbroder opened this issue Nov 9, 2019 · 8 comments
Closed

[Question] Conditional Rendering with forwardRef #167

mrtnbroder opened this issue Nov 9, 2019 · 8 comments
Assignees
Labels
good first issue Good for newcomers wontfix This will not be worked on

Comments

@mrtnbroder
Copy link

mrtnbroder commented Nov 9, 2019

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!

@mrtnbroder mrtnbroder added the good first issue Good for newcomers label Nov 9, 2019
@swyxio
Copy link
Collaborator

swyxio commented Nov 28, 2019

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

swyxio added a commit that referenced this issue Nov 28, 2019
@mrtnbroder
Copy link
Author

No worries. I got it working - yes, but I have to use a workaround with forwardRef prop. I was wondering if this can be achieved with React.forwardRef<A, B> as well.

@ferdaber
Copy link
Collaborator

ferdaber commented Dec 5, 2019

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.

@swyxio swyxio closed this as completed Dec 17, 2019
@O4epegb
Copy link

O4epegb commented Dec 25, 2020

This example actually gives an error with TS 4.1.3 when you pass ref to the component:

Link

@swyxio swyxio reopened this Dec 26, 2020
@swyxio
Copy link
Collaborator

swyxio commented Dec 26, 2020

havent had a chance to look but fix welcome

@O4epegb
Copy link

O4epegb commented Dec 26, 2020

So I've tried to hack some things, seems like it works, but maybe there is a better way to type it?

Link

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

@swyxio
Copy link
Collaborator

swyxio commented Dec 29, 2020

great submission! im not spending any time to validate this right now, but i'll update the docs if anyone else does. thank you!

@stale
Copy link

stale bot commented Feb 27, 2021

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!

@stale stale bot added the wontfix This will not be worked on label Feb 27, 2021
@stale stale bot closed this as completed Mar 6, 2021
bernssolg added a commit to bernssolg/My-React-Sample that referenced this issue Feb 28, 2022
erinodev added a commit to erinodev/My-React-project that referenced this issue Feb 28, 2022
petardev101 added a commit to petardev101/react that referenced this issue Jun 4, 2022
supercrytoking added a commit to supercrytoking/react that referenced this issue Jul 14, 2022
kevindavies8 added a commit to kevindavies8/react-full-stack-developer that referenced this issue Aug 24, 2022
johnfrench3 pushed a commit to johnfrench3/react-Fronted-developer that referenced this issue Sep 7, 2022
ericbrown2716 added a commit to ericbrown2716/react-stack-build-website that referenced this issue Sep 29, 2022
peterjohnson4987 added a commit to peterjohnson4987/full-stack-developer-react that referenced this issue Oct 3, 2022
renawolford6 pushed a commit to renawolford6/react-husky-website that referenced this issue Oct 6, 2022
Yoshidayoshi23 added a commit to Yoshidayoshi23/react that referenced this issue Oct 20, 2022
renawolford6 added a commit to renawolford6/react-dev-build-doc- that referenced this issue Nov 10, 2022
coopfeathy added a commit to coopfeathy/cheatsheet that referenced this issue Dec 4, 2022
dreamcoder75 added a commit to dreamcoder75/react-sample that referenced this issue Jan 15, 2023
holyblock pushed a commit to holyblock/chart that referenced this issue Feb 27, 2023
AIDevMonster added a commit to AIDevMonster/Awesome-React that referenced this issue Jun 21, 2023
whiteghostDev added a commit to whiteghostDev/Awesome-React that referenced this issue Aug 6, 2023
cedev935 added a commit to cedev935/React-TypeScript that referenced this issue Sep 11, 2023
aleksandaralek added a commit to aleksandaralek/typescript-react-cheatsheet that referenced this issue Oct 24, 2023
xbucks pushed a commit to xbucks/react-cheatsheets that referenced this issue Oct 24, 2023
joyfulmagician added a commit to joyfulmagician/react that referenced this issue Oct 25, 2023
KonohaBrain125 pushed a commit to KonohaBrain125/React-Typescript that referenced this issue Oct 26, 2023
TOP-10-DEV added a commit to TOP-10-DEV/typescript-cheatsheets-react that referenced this issue Dec 8, 2023
secretsuperstar1109 added a commit to secretsuperstar1109/react-typescript-cheatsheets that referenced this issue Dec 9, 2023
champion119 added a commit to champion119/react that referenced this issue Jan 5, 2024
dragon360-dev added a commit to dragon360-dev/react that referenced this issue Mar 13, 2024
EugeneYoona added a commit to EugeneYoona/React_full_src that referenced this issue Apr 10, 2024
fairskyDev0201 added a commit to fairskyDev0201/typescript-cheatsheet that referenced this issue Apr 17, 2024
TechSolutionNinja added a commit to TechSolutionNinja/react that referenced this issue May 27, 2024
alisenola added a commit to alisenola/react-cheatsheets that referenced this issue May 29, 2024
LegendaryDev320 added a commit to LegendaryDev320/react that referenced this issue Jun 5, 2024
kaleb0402 added a commit to kaleb0402/react that referenced this issue Jul 18, 2024
Linda423 added a commit to Linda423/React that referenced this issue Jul 31, 2024
tosky19941209 added a commit to tosky19941209/React that referenced this issue Aug 7, 2024
touchsky941209 added a commit to touchsky941209/react-rebrand that referenced this issue Aug 26, 2024
genie4viz pushed a commit to genie4viz/react that referenced this issue Sep 2, 2024
hussammousa68 added a commit to hussammousa68/react that referenced this issue Oct 13, 2024
zeus-soft-world added a commit to zeus-soft-world/React-TypeScript that referenced this issue Oct 15, 2024
chivalrousdev added a commit to chivalrousdev/React that referenced this issue Nov 14, 2024
ElegantDev43 added a commit to ElegantDev43/react-test that referenced this issue Dec 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

4 participants