1. Don’t use public accessor within classes

Don’t:

Do:

Why?

All members within class are public by default (and always public in runtime, TS private/protected will "hide" particular class properties/methods only during compile time). Don't introduce extra churn to your codebase. Also using public accessor is not "valid/idiomatic javascript"

2. Don’t use private accessor within Component class

Don’t:

Good:

Better:

Why:

private accessor won't make your properties/methods on class private during runtime. It's just TypeScript "emulation during compile time". Don't get fooled and make things "private" by using well known patterns like:

name starting with underscore 👉 _someProp

or if you really wanna make those properties private use Symbol for defining those. ( real runtime private properties are coming to ECMAscript )

In reality, you should almost never need to work directly with React Component instance nor accessing its class properties.

3. Don’t use protected accessor within Component class

Don’t:

Do:

Why:

Using protected is an immediate "RED ALERT" 🚨🚨🚨 in terms of functional patterns leverage with React. There are more effective patterns like this for extending behaviour of some component. You can use:

just extract the logic to separate component and use it as seen above

HoC (high order function) and functional composition .

(high order function) and . CaaF ( children as a function )

4. Don’t use enum

Don’t:

Good:

If you need to support runtime enums use following pattern:

Better:

If you don’t need to support runtime enums, all you need to use are type literals:

Why?:

To use enum within TypeScript might be very tempting, especially if you're coming from language like C# or Java. But there are better ways how to interpret both with well known JS idiomatic patterns or as can be seen in "Better" example just by using compile time type literals.

Enums compiled output generates unnecessary boilerplate (which can be mitigated with const enum though. Also string enums are better in this one)

Non string Enums don’t narrow to proper number type literal, which can introduce unhandled bug within your app

It’s not standard/idiomatic JavaScript (although enum is reserved word in ECMA standard)

is reserved word in ECMA standard) Cannot be used with babel for transpiling 👀

🙇‍Enum helper

In our “Good” example, you might think like, ugh that’s a lot of boilerplate dude! I hear you my friends. Loud and clear 🙏

If you need to support runtime enums for various reasons, you can leverage small utility function from rex-tils library like showcased here:

5. Don’t use constructor for class Components

Don’t:

Do:

Why:

There is really no need to use constructor within React Component.

If you do so, you need to provide more code boilerplate and also need to call super with provided props ( if you forget to pass props to your super, your component will contain bugs as props will not be propagated correctly).

But… but… hey ! React official docs use constructor! 👉 That’s fine (React team uses current version of JS to showcase stuff) But… but…, class properties are not standard JavaScript! 👉 Class fields are in Stage 3, which means they are gonna be implemented in JS soon

Initializing state with some logic

Of course you may ask, what if I need to introduce some logic to initialize component state, or even to initialize the state from some dependant values, like props for example.

Answer to your question is rather straightforward.

Just define a pure function outside the component with your custom logic (as a “side effect” you’ll get easily tested code as well 👌).

6. Don’t use decorators for class Components

Don’t:

Good:

Better:

Why:

Decorators are parasitic 🐛 👀 🤢

You won’t be able to get original/clean version of your class.

TypeScript uses old version of decorator proposal which isn’t gonna be implemented in ECMAscript standard 🚨.

It adds additional runtime code and processing time execution to your app.

What is most important though, in terms of type checking within JSX, is, that decorators don’t extend class type definition. That means (in our example), that our Container component, will have absolutely no type information for consumer about added/removed props.

7. Use lookup types for accessing component State/Props types

Don’t:

Do:

Why:

Exporting Props or State from your component implementation is making your API surface bigger.

You should always ask a question, why consumers of your component should be able to import explicit State/Props type? If they really need that, they can always access it via type lookup functionality. So cleaner API but type information is still there for everyone. Win Win 💪

If you need to provide a complex Props type though, it should be extracted to models/types file exported as Public API.

8. Always provide explicit type for children Props

Don’t:

Do:

Why:

children prop is annotated as optional within both Component and Functional Component in react.d.ts which just mirrors the implementation how React handles children. While that's ok and everything, I prefer to be explicit with component API.

prop is annotated as optional within both Component and Functional Component in which just mirrors the implementation how React handles children. While that's ok and everything, I prefer to be explicit with component API. if you plan to use children for content projection, make sure to explicit annotate it with type of your choosing and in opposite, if your component doesn't use it, prevent it's usage with never type.

Children type constraint 🚸

Hey, mister Skateboarder ! I have a question ✋:

What types can be used for children annotation in TypeScript ? I mean, can I constraint children to be only a particular type of Component (like is possible with Flow) ? Something like Tab within Tabs children: Tab[] ?

Unfortunately not 🙃, as TypeScript isn’t able to “parse” output of JSX.factory 👉 React.createElement which returns JSX.Element from global namespace, which extends React.ReactElement<any> so what compiler gets is an object type, with type checking turned off (WARNING:every time you any a kitten dies 🙀😅)

Or as stated in TypeScript docs 👀:

“By default the result of a JSX expression is typed as any . You can customize the type by specifying the JSX.Element interface. However, it is not possible to retrieve type information about the element, attributes or children of the JSX from this interface. It is a black box ⬛️ 📦." NOTE: TS 2.8 introduced locally scoped JSX namespaces, which may help to resolve this feature in the future. Watch this space!

We can use following types for annotating children :

ReactNode | ReactChild | ReactElement

| | object | {[key:string]:unknown} | MyModel

| | primitives string | number | boolean

| | Array<T> where T can be any of former

where T can be any of former never | null | undefined ( null and undefined doesn't make much sense though )

9. Use type inference for defining Component State or DefaultProps

Don’t:

Good:

Better:

By making freezing initialState/defaultProps, type system will infer correct readonly types (when someone would accidentally set some value, he would get compile error). Also marking both static defaultProps and state as readonly within the class, is a nice touch, to prevent us from making any runtime errors when incorrectly setting state via this.state = {...}

Why:

Type information is always synced with implementation as source of truth is only one thing 👉 THE IMPLEMENTATION! 💙

💙 Less type boilerplate

More readable code

by adding readonly modifier and freezing the object, any mutation within your component will immediately end with compile error, which will prevent any runtime error = happy consumers of your app!

What if I wanna use more complicated type within state or default props?

Use as operator to cast your properties within the constant.

Example:

How to infer state type if I wanna use derived state from props?