Wrapping/Mirroring
Wrapping/Mirroring a HTML Element
Usecase: you want to make a <Button>
that takes all the normal props of <button>
and does extra stuff.
Strategy: extend React.ComponentPropsWithoutRef<'button'>
Forwarding Refs: As the React docs themselves note, most usecases will not need to obtain a ref to the inner element. But for people making reusable component libraries, you will need to forwardRef
the underlying element, and then you can use ComponentPropsWithRef
to grab props for your wrapper component. Check our docs on forwarding Refs for more.
In future, the need to forwardRef
may go away in React 17+, but for now we still have to deal with this. ๐
Why not ComponentProps
or IntrinsicElements
or [Element]HTMLAttributes
or HTMLProps
or HTMLAttributes
?
ComponentProps
or IntrinsicElements
or [Element]HTMLAttributes
or HTMLProps
or HTMLAttributes
?ComponentProps
You CAN use ComponentProps
in place of ComponentPropsWithRef
, but you may prefer to be explicit about whether or not the component's refs are forwarded, which is what we have chosen to demonstrate. The tradeoff is slightly more intimidating terminology.
More info: https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/forward_and_create_ref/
Maybe JSX.IntrinsicElements
or React.[Element]HTMLAttributes
There are at least 2 other equivalent ways to do this, but they are more verbose:
Looking at the source for ComponentProps
shows that this is a clever wrapper for JSX.IntrinsicElements
, whereas the second method relies on specialized interfaces with unfamiliar naming/capitalization.
Note: There are over 50 of these specialized interfaces available - look for
HTMLAttributes
in our@types/react
commentary.
Ultimately, we picked the ComponentProps
method as it involves the least TS specific jargon and has the most ease of use. But you'll be fine with either of these methods if you prefer.
Definitely not React.HTMLProps
or React.HTMLAttributes
This is what happens when you use React.HTMLProps
:
It infers a too-wide type of string
for type
, because it uses AllHTMLAttributes
under the hood.
This is what happens when you use React.HTMLAttributes
:
Wrapping/Mirroring a Component
TODO: this section needs work to make it simplified.
Usecase: same as above, but for a React Component you don't have access to the underlying props
Strategy: extract a component's props by inferring them
Example:
Usage:
thanks dmisdm
:new: You should also consider whether to explicitly forward refs:
Polymorphic Components (e.g. with as
props)
"Polymorphic Components" = passing a component to be rendered, e.g. with
as
props
ElementType
is pretty useful to cover most types that can be passed to createElement e.g.
You might also see this with React Router:
For more info you can refer to these resources:
- https://blog.andrewbran.ch/polymorphic-react-components/
- https://github.com/kripod/react-polymorphic-box
- https://stackoverflow.com/questions/58200824/generic-react-typescript-component-with-as-prop-able-to-render-any-valid-dom
Thanks @eps1lon and @karol-majewski for thoughts!
Generic Components
Just as you can make generic functions and classes in TypeScript, you can also make generic components to take advantage of the type system for reusable type safety. Both Props and State can take advantage of the same generic types, although it probably makes more sense for Props than for State. You can then use the generic type to annotate types of any variables defined inside your function / class scope.
You can then use the generic components and get nice type safety through type inference:
As of TS 2.9, you can also supply the type parameter in your JSX to opt out of type inference:
You can also use Generics using fat arrow function style:
The same for using classes: (Credit: Karol Majewski's gist)
Though you can't use Generic Type Parameters for Static Members:
To fix this you need to convert your static function to a type inferred function:
Generic components with children
children
is usually not defined as a part of the props type. Unless children
are explicitly defined as a part of the props
type, an attempt to use props.children
in JSX or in the function body will fail:
View in the TypeScript Playground
To work around that, either add children
to the WrapperProps
definition (possibly narrowing down its type, as needed):
or wrap the type of the props in React.PropsWithChildren
(this is what React.FC<>
does):
Typing Children
Some API designs require some restriction on children
passed to a parent component. It is common to want to enforce these in types, but you should be aware of limitations to this ability.
What You CAN Do
You can type the structure of your children: just one child, or a tuple of children.
The following are valid:
Don't forget that you can also use `prop-types` if TS fails you.
What You CANNOT Do
The thing you cannot do is specify which components the children are, e.g. If you want to express the fact that "React Router <Routes>
can only have <Route>
as children, nothing else is allowed" in TypeScript.
This is because when you write a JSX expression (const foo = <MyComponent foo='foo' />
), the resultant type is blackboxed into a generic JSX.Element type. (thanks @ferdaber)
Type Narrowing based on Props
What you want:
How to implement: Use type guards!
View in the TypeScript Playground
Components, and JSX in general, are analogous to functions. When a component can render differently based on their props, it's similar to how a function can be overloaded to have multiple call signatures. In the same way, you can overload a function component's call signature to list all of its different "versions".
A very common use case for this is to render something as either a button or an anchor, based on if it receives a href
attribute.
They don't even need to be completely different props, as long as they have at least one difference in properties:
Approach: Generic Components
Here is an example solution, see the further discussion for other solutions. thanks to @jpavon
Approach: Composition
If you want to conditionally render a component, sometimes is better to use React's composition model to have simpler components and better to understand typings:
You may also want to use Discriminated Unions, please check out Expressive React Component APIs with Discriminated Unions.
Here is a brief intuition for Discriminated Union Types:
Take care: TypeScript does not narrow the type of a Discriminated Union on the basis of typeof checks. The type guard has to be on the value of a key and not it's type.
The above example does not work as we are not checking the value of event.value
but only it's type. Read more about it microsoft/TypeScript#30506 (comment)
Discriminated Unions in TypeScript can also work with hook dependencies in React. The type matched is automatically updated when the corresponding union member based on which a hook depends, changes. Expand more to see an example usecase.
In the above example, based on the isArray
union member, the type of the value
hook dependency changes.
To streamline this you may also combine this with the concept of User-Defined Type Guards:
Read more about User-Defined Type Guards in the Handbook.
Props: One or the Other but not Both
Use the in
keyword, function overloading, and union types to make components that take either one or another sets of props, but not both:
View in the TypeScript Playground
Further reading: how to ban passing {}
if you have a NoFields
type.
Props: Must Pass Both
Thanks diegohaz
Props: Pass One ONLY IF the Other Is Passed
Say you want a Text component that gets truncated if truncate
prop is passed but expands to show the full text when expanded
prop is passed (e.g. when the user clicks the text).
You want to allow expanded
to be passed only if truncate
is also passed, because there is no use for expanded
if the text is not truncated.
Usage example:
You can implement this by function overloads:
Props: Omit prop from a type
Note: Omit was added as a first class utility in TS 3.5! ๐
Sometimes when intersecting types, we want to define our own version of a prop. For example, I want my component to have a label
, but the type I am intersecting with also has a label
prop. Here's how to extract that out:
When your component defines multiple props, chances of those conflicts increase. However you can explicitly state that all your fields should be removed from the underlying component using the keyof
operator:
As you can see from the Omit example above, you can write significant logic in your types as well. type-zoo is a nice toolkit of operators you may wish to check out (includes Omit), as well as utility-types (especially for those migrating from Flow).
Props: Extracting Prop Types of a Component
Sometimes you want the prop types of a component, but it isn't exported.
A simple solution is to use React.ComponentProps
:
There are advanced edge cases if you want to extract the prop types of a component taking into account internal props, propTypes
, and defaultProps
- check our issue here for helper utilities that resolve these.
Props: Render Props
Advice: Where possible, you should try to use Hooks instead of Render Props. We include this merely for completeness.
Sometimes you will want to write a function that can take a React element or a string or something else as a prop. The best Type to use for such a situation is React.ReactNode
which fits anywhere a normal, well, React Node would fit:
If you are using a function-as-a-child render prop:
Something to add? File an issue.
Handling Exceptions
You can provide good information when bad things happen.
Simply throwing an exception is fine, however it would be nice to make TypeScript remind the consumer of your code to handle your exception. We can do that just by returning instead of throwing:
You can also describe exceptions with special-purpose data types (don't say monads...) like the Try
, Option
(or Maybe
), and Either
data types: