After working in several companies, I've noticed that the code is always typed differently and this can lead to some issues with typings in the future.
I want to show you how to make code better typed the way you probably didn't know.
Function Component type
First of all, we have to figure out what is the best way to define a function component.
Usually developers do initialise components using default functions so the code would look like this:
function SomeComponent(props: SomeComponentProps): JSX.Element {
...
}
Such approach has several disadvantages:
- You always need to write the return type
- If your component accepts any children you need to define the
children
property in your Props type and set the type union type likenull | ReactNode | ReactNode[]
ornull | ReactElement
– Since you need to use one of types above for your children, you have to import them too
All of these problems make your code more "boilerplaty" and make your code less readable spending more time than you can.
The best solution for this is FC
or FunctionComponent
types. FC
is just a shorthand for FunctionComponent
– You can check this here:
type FC<P = {}> = FunctionComponent<P>;
Let's see what this FunctionComponent
is:
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P> | undefined;
contextTypes?: ValidationMap<any> | undefined;
defaultProps?: Partial<P> | undefined;
displayName?: string | undefined;
}
Here we also need to check the PropsWithChildren
type to make sure what we will pass to the component's props:
type PropsWithChildren<P> = P & { children?: ReactNode };
Now let's see how FC-typed component looks like:
const SomeComponent: FC<SomeComponentProps> = ({ message }) => (
<div>{message}</div>
);
Current solution solves such problems:
- We don't need to define children¹ type anymore
- We separate values from types moving generic type to the beginning making the code more readable
- We don't need to write a function return type
- Since we are using arrow function², we can omit curly braces in some cases and return jsx directly
- Using the same component type everywhere we prevent inconsistency of types that can lead to waste of time because of type definition instead of spending it to solve actual problems
1 - Keep in mind that it is planned to remove children
as default property from FunctionComponent
in @types/react@^18.0.0
. In the React 18 you should define children property manually and set ReactNode
type to it.
2 – If you are using React Developer Tools you should notice that arrow functions don't have displayName
by default so you should define it manually:
SomeComponent.displayName = 'SomeComponent';
Type of regular HTML props / attributes
You've probably had problems with props definition for components that pass most of their props further to the regular html element.
Previously I've seen solutions like importing HTMLAttributes
from react
module and then passing HTML...Element
type to it as a generic argument:
type SomeType = HTMLAttributes<HTMLInputElement>;
This type is not much reusable because we cannot get props of the custom component and this is where ComponentProps
comes:
// regular HTML input element
type InputElementProps = ComponentProps<'input'>;
// ComponentProps works for both regular elements and the custom ones
type CustomInputProps = ComponentProps<typeof CustomInput>;
CSS property type
If you use css-in-js solutions, sometimes you want your component to accept certain CSS-properties passed deeper to the component's styles.
You can just set the property type as a type union: number | string
but it is unspecific and can lead to unpredicted behaviour because of typos when using such properties as position
, flex-direction
, etc.
The better solution is to use CSSProperties
type exported from react
module:
interface SomeComponentProps {
display: CSSProperties['display']
}
Such little things may improve the quality of your code and prevent you from having pain with Typescript.
Top comments (0)