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

How to handle conditional rendering? #12

Closed
smashercosmo opened this issue Jun 4, 2018 · 19 comments
Closed

How to handle conditional rendering? #12

smashercosmo opened this issue Jun 4, 2018 · 19 comments

Comments

@smashercosmo
Copy link

Consider the following example:

type Component1Props = { ... }
const Component1: React.SFC<Component1Props> = props => { ... }

type Component2Props = { ... }
const Component2: React.SFC<Component2Props> = props => { ... }

const Component3: React.SFC<???> = props => {
   // some prop from Component1Props
   if(props.prop1) {
      return <Component1 {...props} />
   }
   
   // some prop from Component2Props
   if(props.prop2) {
      return <Component2 {...props} />
   }
}

Ideally I want typechecker to handle two things:

  1. It's required to pass either prop1 or prop2, but not both of them
  2. If prop1 is passed then props are of type Component1Props and if prop2 is passed then props are of type Component2Props

P. S. I'm completely new to TypeScript and I'm not sure that it's even possible)

@ghost
Copy link

ghost commented Jun 4, 2018

I will try to check more but for a quick feedback try:

type IOneOfThem = Component1Props | Component2Props;

@smashercosmo
Copy link
Author

Yeah, that was the first thing that I tried. Didn't work)

@ghost
Copy link

ghost commented Jun 4, 2018

Will try to check this in the next two hours

@tsiq-swyx
Copy link
Contributor

this works for me... does that fit your use case @smashercosmo

type Component1Props = { a1: boolean };
const Component1: React.SFC<Component1Props> = props => <div>{props.a1}</div>;

type Component2Props = { a2: boolean };
const Component2: React.SFC<Component2Props> = props => <div>{props.a2}</div>;

const Component3 = (props: {showprop1: boolean} & (Component1Props | Component2Props)) => {
   // some prop from Component1Props
   const {showprop1, ...rest} = props;
   if (showprop1) {
      return <Component1 {...rest as Component1Props} />;
   } else {
      return <Component2 {...rest as Component2Props} />;
   }
};

@smashercosmo
Copy link
Author

Well, not exactly) Consider real-life example

import React from 'react'
import { NavLink, NavLinkProps } from 'react-router-dom'

type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>

const Link = (props: ???) => {
   if (props.to) {
      return <NavLink {...props as NavLinkProps} />;
   } else {
      return <a {...props as AnchorProps} />;
   }
};

export default Link;

If user passes 'to' property NavLink should be rendered, if user passes 'href' then simple anchor tag should be rendered, but passing both 'to' and 'href' should result in compilation error). So at least one of two properties (to and href) is required, but passing both of them is forbidden.

P.S. Why aren't you using React.SFC in 3rd component?

@tsiq-swyx
Copy link
Contributor

cos i dont like React.SFC haha

hmm, so you want compile time error if both to and href are passed. this is stumping me.

@ghost
Copy link

ghost commented Jun 4, 2018

I think that is related more to TypeScript than being related to TypeScript-React.

I was busy today.. but I'll invest some time in the next hours

@jpavon
Copy link
Contributor

jpavon commented Jun 4, 2018

This seems to work but it feels too much boilerplate for little gain, maybe there is a better way to do it. (Note that NavLinkProps contains 'href' so you need to omit it to work as you expect)

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

interface LinkProps {}

type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement>
type RouterLinkProps = Omit<NavLinkProps, 'href'>

const Link = <T extends {}>(
    props: LinkProps & T extends RouterLinkProps ? RouterLinkProps : AnchorProps
) => {
    if ((props as RouterLinkProps).to) {
        return <NavLink {...props as RouterLinkProps} />
    } else {
        return <a {...props as AnchorProps} />
    }
}

<Link<RouterLinkProps> to="/">My link</Link> // ok
<Link<AnchorProps> href="/">My link</Link> // ok
<Link<RouterLinkProps> to="/" href="/">My link</Link> // error

@tsiq-swyx
Copy link
Contributor

tsiq-swyx commented Jun 4, 2018

wow. i think writing a React component with a generic type like that should definitely be included in this list, i really really like it. i wonder if we can boil this example down a bit simpler though:

// this is just a draft, i dont know what i'm really going for here yet
const Link = <T>(props: LinkProps & T) => {
	return <NavLink {...props} />
}

i realize this doesnt answer OP's original question, im just trying to figure out what i can add to the cheatsheet for this general category of problem. something like a polymorphic react component.

@ghost
Copy link

ghost commented Jun 4, 2018

I used the generic + props merging when I used abstract React Class Components.. it's so powerful

@codepunkt
Copy link
Contributor

Does this help?

interface IComponent1Props {
  foo: string
}

interface IComponent2Props {
  bar: string
}

const Component1: React.SFC<IComponent1Props> = ({ foo }) => (
  <div>component 1: {foo}</div>
)
const Component2: React.SFC<IComponent2Props> = ({ bar }) => (
  <div>component 2: {bar}</div>
)

type Foo = { prop1: true } & IComponent1Props
type Bar = { prop2: true } & IComponent2Props

const isFoo = (test: Foo | Bar): test is Foo => {
  return (test as Foo).prop1 === true
}

const isBar = (test: Foo | Bar): test is Bar => {
  return (test as Bar).prop2 === true
}

const Component3: React.SFC<Foo | Bar> = (props) => {
  if (isFoo(props)) {
    const { prop1, ...rest } = props
    return <Component1 {...rest} />
  } else if (isBar(props)) {
    const { prop2, ...rest } = props
    return <Component2 {...rest} />
  }

  return null
}

@tsiq-swyx
Copy link
Contributor

this is verbose but very readable. didnt know you could do tests like that!

i am going to add a link to this discussion. this has been very helpful to me.

tsiq-swyx added a commit that referenced this issue Jul 11, 2018
@codepunkt
Copy link
Contributor

You're welcome. I was lazy so i didn't name the types well 😆

@smashercosmo
Copy link
Author

@codepunkt you solution can be implemented with much less code using Discriminated Unions, but still it's annoying to specify extra props to differentiate one component from another.

@jpavon solution works, but again, it's annoying to specify generic type every time

I wonder why type guards don't work

import React from 'react'
import {
  Link as ReactRouterLink,
  LinkProps as ReactRouterLinkProps,
} from 'react-router-dom'

type LinkProps =
  | ReactRouterLinkProps
  | React.AnchorHTMLAttributes<HTMLAnchorElement>

function isAnchorProps(
  props: LinkProps,
): props is React.AnchorHTMLAttributes<HTMLAnchorElement> {
  return (
    (props as React.AnchorHTMLAttributes<HTMLAnchorElement>).href !== undefined
  )
}

export function Link(props: LinkProps) {
  if (isAnchorProps(props)) {
    return <a {...props} />
  }

  return <ReactRouterLink {...props} />
}

If I then use Link component like this

<Link href="/hi" to="/hi">
    Hello
</Link>

TypeScript doesn't raise any error

@swyxio
Copy link
Collaborator

swyxio commented May 16, 2019

@azizhk
Copy link
Collaborator

azizhk commented Jul 17, 2019

There is this amazing blog by @andrewbranch on Discriminated Unions:
https://blog.andrewbran.ch/expressive-react-component-apis-with-discriminated-unions/

Can we add this somewhere in the cheat-sheet readme. (Amazing blog, hover over variables in the snippets and see the definition)

There are a few caveats, mentioned here and also in issues microsoft/TypeScript#29703 and microsoft/TypeScript#28131

@ferdaber
Copy link
Collaborator

ferdaber commented Jul 17, 2019

I was very late to this party, but the recommended way of doing conditional rendering is a generic function for function components (or generic constructors for class components), unfortunately that does mean you can't use the SFC annotation. Everyone's already covered what I was going to add 🙃

@swyxio
Copy link
Collaborator

swyxio commented Jul 17, 2019

@azizhk yup i already put it in https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/blob/master/ADVANCED.md#typing-a-component-that-accepts-different-props but its a bit buried. i havent given much thought to how this section can be organized but i hope that people read every bit thoroughly and folow through on the links.

gonna consider this closed for now, open a new issue if people wanna re-ask stuff :)

@swyxio swyxio closed this as completed Jul 17, 2019
@andrewbranch
Copy link

(Thanks for the kind words, @azizhk 😄)

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 pushed 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 pushed 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
blackphantom0221 added a commit to blackphantom0221/typescript-cheatsheets-react that referenced this issue Jan 23, 2023
tamatashi pushed a commit to tamatashi/chart that referenced this issue Feb 27, 2023
spartacus0816 pushed a commit to spartacus0816/react_frontend_master that referenced this issue May 20, 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 pushed 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
le-pus pushed a commit to le-pus/React-Typescript that referenced this issue Oct 26, 2023
atsumi000105 added a commit to atsumi000105/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
SManOlaf38 pushed a commit to SManOlaf38/react_typescript that referenced this issue Feb 20, 2024
SManOlaf38 added a commit to SManOlaf38/react_typescript that referenced this issue Feb 20, 2024
rising-dragon360 added a commit to rising-dragon360/react that referenced this issue Mar 13, 2024
EugeneYoona added a commit to EugeneYoona/React_full_src that referenced this issue Apr 10, 2024
snowMan128 added a commit to snowMan128/TypeReact that referenced this issue Apr 15, 2024
fairskyDev0201 added a commit to fairskyDev0201/typescript-cheatsheet that referenced this issue Apr 17, 2024
bluesky4293 added a commit to bluesky4293/React that referenced this issue Apr 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants