最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to type any param with React Typescript in Functional Component - Stack Overflow

programmeradmin8浏览0评论

I have an existing javascript ponent:

const RenderState = ({ state, else: elseChild = undefined, ...states }) => {
  if (state && states[state]) return states[state]
  if (elseChild) return elseChild
  return null
}

export default RenderState

I am trying to convert it to typscript and have so far:

import React from 'react'

interface Props {
  state: string
  else?: JSX.Element
  any: JSX.Element
}

const RenderState: React.FC<Props> = ({ state, else: elseChild = undefined, ...states }) => {
  if (state && states[state]) return states[state]
  if (elseChild) return elseChild
  return null
}

export default RenderState

The problem I am having is of course that any: JSX.Element is looking for a prop literally called 'any' when instead I want to allow any prop as a JSX element, such as custom in the following example

Here is an example use case of this ponent:

import React from 'react'
import RenderState from './RenderState'

const MyComp = () => {
  const [state, setState] = React.useState<string | null>(null)
  <>
    <Button onClick={()=>{setState(null)}}>Default</Button>
    <Button onClick={()=>{setState('custom')}}>Custom</Button>
    <RenderState
      state={state}
      custom={(<p>Some custom content here...</p>)}
      else={(<p>Some default content here...</p>)}
    />
  </>
}

I have an existing javascript ponent:

const RenderState = ({ state, else: elseChild = undefined, ...states }) => {
  if (state && states[state]) return states[state]
  if (elseChild) return elseChild
  return null
}

export default RenderState

I am trying to convert it to typscript and have so far:

import React from 'react'

interface Props {
  state: string
  else?: JSX.Element
  any: JSX.Element
}

const RenderState: React.FC<Props> = ({ state, else: elseChild = undefined, ...states }) => {
  if (state && states[state]) return states[state]
  if (elseChild) return elseChild
  return null
}

export default RenderState

The problem I am having is of course that any: JSX.Element is looking for a prop literally called 'any' when instead I want to allow any prop as a JSX element, such as custom in the following example

Here is an example use case of this ponent:

import React from 'react'
import RenderState from './RenderState'

const MyComp = () => {
  const [state, setState] = React.useState<string | null>(null)
  <>
    <Button onClick={()=>{setState(null)}}>Default</Button>
    <Button onClick={()=>{setState('custom')}}>Custom</Button>
    <RenderState
      state={state}
      custom={(<p>Some custom content here...</p>)}
      else={(<p>Some default content here...</p>)}
    />
  </>
}
Share Improve this question asked Nov 25, 2020 at 18:19 amasteramaster 2,1635 gold badges28 silver badges55 bronze badges 1
  • Those seem like bad prop names to me. Best not to use existing JS/TS keywords as your own... – paddotk Commented Jun 10, 2021 at 8:55
Add a ment  | 

1 Answer 1

Reset to default 7 +50

Sonds like you need index signature on your type. A naive approach would be

interface Props {
  state: string
  else?: JSX.Element
  [key: string]: JSX.Element
}

However, this won't pile - because index signature on an interface requires that all named properties (state and else) are patible with it.

Instead you can rewrite it in a form of type intersection:

type Props = {
  state: string
  else?: JSX.Element
} & Record<string, JSX.Element>

This still won't pile for the same, but gives us a clue. What if instead of string for keys we have a well-known set of possible values? Next iteration would be

type Key = "custom" | "another";

type Props = {
  state: Key | null
  else?: JSX.Element
} & Record<Key, JSX.Element>

This looks better. But how do we make it reusable, so that the same ponent could be used with different lists of possible Keys? Let's make it generic!

So the final code:

type Props<Key extends string> = {
  state: Key | null
  else?: JSX.Element
} & Record<Key, JSX.Element>

You need to make the ponent a generic function as well:

function RenderState<Key>(props: Props<Key>) { ... }

And use it in your app:

    const [state, setState] = React.useState<"custom" | null>(null);
    <RenderState
      state={state}
      custom={(<p>Some custom content here...</p>)}
      else={(<p>Some default content here...</p>)}
    />

Edit 1

This approach allows to write props[props.state] inside RenderState (the result is inferred to JSX.Element). However, it fails to pile with rest destructuring, as in the original code snippet:

const {state, else: elseEl, ...rest} = props;
rest[props] // TS error here

It reveals a flaw in our type definition. We need to make sure state value doesn't include "state" and "else" literals, to avoid type conflict.

Updated type would be


type Props<Key extends string> = {
  state: Exclude<Key, "state" | "else"> | null
  else?: JSX.Element
} & Record<Key, JSX.Element>

Now this should pile well

const {state, else: elseEl, ...rest} = props;
rest[props]
发布评论

评论列表(0)

  1. 暂无评论