typescript-cheatsheets / react

Cheatsheets for experienced React developers getting started with TypeScript

Home Page:https://react-typescript-cheatsheet.netlify.app

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Question] Doing a component map

neo opened this issue · comments

Playground

I'm trying to do a classic component map by a type id like this:

const map = {
  "one": ComponentOne,
  "two": ComponentTwo,
}

and then use it by type id and hopefully it would be able to also infer the type of the prop for the corresponding component; how should I do it?

add as const to the end of your object - I'm not sure if this is something we want in the cheatsheet though, it's very much plain 'ol typescript stuff

I think your looking for something like this:

import React from "react"

function One({oneProp}: {oneProp: number}) {
  return <div>{oneProp}</div>
}

function Two({twoProp}: {twoProp: string}) {
  return <div>{twoProp}</div>
}

const map = {
  one: One,
  two: Two,
}

function Switch<T extends keyof typeof map>({type, props}: {type: T, props: React.ComponentProps<typeof map[T]>}) { 
  const Component = map[type]
  return <Component {...props} />
}

function Page() {
  return <>
    <Switch type="one" props={{oneProp: 2}} />
    <Switch type="two" props={{twoProp: "hello"}} />
  </>
}

However, <Component {...props} /> will have a TypeScript error here, because TypeScript doesn't really understand the relationship we provided in map. Since we know this is safe, we could solve it with <Component {...props as any} />. But then again, anything can be "solved" with any.

Another way is to use a union instead of generic:

function Switch({type, props}: {type: "one", props: React.ComponentProps<typeof One>} | {type: "two", props: React.ComponentProps<typeof Two>}) { 
  const Component = map[type]
  return <Component {...props as any} />
}

But we still need as any there.

A solution without any is to use switch instead:

import React from "react"

interface OneProps {
  oneProp: number
}

function One({oneProp}: OneProps) {
  return <div>{oneProp}</div>
}

interface TwoProps {
  twoProp: string
}

function Two({twoProp}: TwoProps) {
  return <div>{twoProp}</div>
}

function Switch({type, props}: {type: "one", props: OneProps} | {type: "two", props: TwoProps}) {
  switch (type) {
    case "one": return <One {...props} />
    case "two": return <Two {...props} />
  }
}

function Page() {
  return <>
    <Switch type="one" props={{oneProp: 2}} />
    <Switch type="two" props={{twoProp: "hello"}} />
  </>
}

A bit less magic, but might be the best solution.

Fun problem to think about, but I agree that this is not really related to React, but rather a TypeScript challenge, so I don't think there's a need to add it to the cheatsheet.

Thank you @filiptammergard for all the details and options provided!! ❤️ (also thank you for understanding my use case exactly!)

I actually had union + switch/case at the end too, but was just wondering if TS can handle such a relatively common pattern – I guess not yet 😂

Not sure if this is worth noting in the cheatsheet, but at least now it's in an issue 😆

Thank you @orta too for the tips!