DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • React, Angular, and Vue.js: What’s the Technical Difference?
  • The Cypress Edge: Next-Level Testing Strategies for React Developers
  • How to Build Scalable Mobile Apps With React Native: A Step-by-Step Guide
  • Mastering React App Configuration With Webpack

Trending

  • Building a Real-Time Change Data Capture Pipeline With Debezium, Kafka, and PostgreSQL
  • How to Convert XLS to XLSX in Java
  • Customer 360: Fraud Detection in Fintech With PySpark and ML
  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 3: Understanding Janus
  1. DZone
  2. Coding
  3. JavaScript
  4. 10 Must-Know Patterns for Writing Clean Code With React and Typescript

10 Must-Know Patterns for Writing Clean Code With React and Typescript

Clean code is a consistent style of programming that makes your code easier to write, read, and maintain. Learn ten useful patterns for React and Typescript.

By 
Alex Omeyer user avatar
Alex Omeyer
·
Feb. 12, 22 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
10.8K Views

Join the DZone community and get the full member experience.

Join For Free

Introduction 

React is a JavaScript library, and it is the most popular and industry-leading frontend development library today. JavaScript is a loosely typed language, and as a result, it catches runtime. The result of this is that JavaScript errors are caught very late and this can lead to nasty bugs. As a JavaScript library, React inherits this problem.

Clean code is a consistent style of programming that makes your code easier to write, read, and maintain. Anyone can write code that a computer can understand but good developers write clean code – code that humans can understand. Clean code is a reader-focused development style that improves our software quality and maintainability.

Writing clean code involves writing codes with clear and simple design patterns that make it easy for humans to read, test, and maintain. Consequently, clean code can lower the cost of software development. And this is because the principles involved in writing clean code eliminate technical debts.

In this article, we would look at some useful patterns to use when working with React and TypeScript.

Use Default Import to Import React

Consider the code below:

 
import * as React from "react";


{% gist https://gist.github.com/lawrenceagles/c95fa549af8a958fd074324fcfb6f73f %}

While the code above works, it is confusing and not a good practice to import all the contents of React if we are not using them. A better pattern is to use default export as seen below:

 
import React, {useContext, useState} from "react";


{% gist https://gist.github.com/lawrenceagles/c6de59538119ee33d9c2e71c64620a56 %}

With this approach, we can destructure what we need from the react module instead of importing all the contents.

Note: To use this option, we need to configure the tsconfig.json file as seen below:

 
{
  "compilerOptions": {
    "esModuleInterop": true
  }
}


{% gist https://gist.github.com/lawrenceagles/d704140bbe003cd05be31b6ae7120468 %}

In the code above, by setting esModuleInterop to true we enable [allowSyntheticDefaultImports](http://allowsyntheticdefaultimports) which is important for TypeScript to support our syntax.

Declare Types Before Runtime Implementation

Consider the code below:

 
import React, {Component} from "react";

const initialState = { count: 1 }
const defaultProps = { name: "John Doe" }

type State = typeof initialState;
type Props = { count?: number } & typeof defaultProps

class Counter extends Component {

   static defaultProps = defaultProps;
   state = initialState;

   // ...

}


{% gist https://gist.github.com/lawrenceagles/f7b5daad358c0e9f3623baa83474ab01 %}

The code above can be cleaner and more readable if we separate the runtime and compile-time declarations. And this is done by declaring the types — the compile type declarations first.

Consider the code below:

 
import React, {Component} from "react";

type State = typeof initialState;
type Props = { count?: number } & typeof defaultProps

const initialState = { count: 1 }
const defaultProps = { name: "John Doe" }

class Counter extends Component {

   static defaultProps = defaultProps;
   state = initialState;

   // ...

}


{% gist https://gist.github.com/lawrenceagles/317170a0b580b92738a94fce7197be17 %}

Now at first glance, a developer knows what the component API looks like since the first line of the code clearly shows this.

Also, we have separated our compile-time declarations from our runtime declarations.

Always Provide an Explicit Type of Children Props

TypeScript mirrors how React handles children props by annotating it as optional in the react.d.ts for both functional and class components. Consequently, we are required to explicitly provide a type for the children props. 

However, it is best practice to always explicitly annotate children props with a type. This is useful in cases where we want to use children for content projection, and if our component does not use it, we can simply annotate it with the never type.

Consider the code below:

 
import React, {Component} from "react";
// Card.tsx
type Props = {
    children: import('react').ReactNode
}

class Card extends Component<Props> {
    render() {
        const {children} = this.props;
        return <div>{children}</div>;
    }
}


{% gist https://gist.github.com/lawrenceagles/45672ae71f63904fa7f6c88ec5a90e75 %}

Below are some valid types to annotate the children props:

  • ReactNode | ReactChild | ReactElement
  • For primitive we can use string | number | boolean
  • Object and Arrays are also valid types
  • never | null | undefined – Note: null and undefined are not recommended

Use Type Inference for Defining a Component State or DefaultProps

Consider the code below:

 
import React, {Component} from "react";

type State = { count: number };

type Props = {
    someProps: string & DefaultProps;
}

type DefaultProps = {
    name: string
}

class Counter extends Component<Props, State> {
    static defaultProps: DefaultProps = {name: "John Doe"}
    state = {count: 0}
  
    // ...
}


{% gist https://gist.github.com/lawrenceagles/05c4c6b8ff5930776a0febab60ce53f8 %}

While the code above works we can refactor it for the following improvements: To enable TypeScript’s type system to correctly infer readonly types such as DefaultProps and initialState To prevent developer bugs arising from accidentally setting state: this.state = {} Consider the code below:

 
import React, {Component} from "react";

const initialState = Object.freeze({ count: 0 })
const defaultProps = Object.freeze({name: "John Doe"})

type State = typeof initialState;
type Props = { someProps: string } & typeof defaultProps;

class Counter extends Component<Props, State> {
    static readonly defaultProps = defaultProps;
    readonly state  = {count: 0}
  
    // ...
}


{% gist https://gist.github.com/lawrenceagles/6e4ea2442091e86b3903738d21f0b613 %}

In the code above, by freezing the DefaultProps and initialState the TypeScript type system can now infer them as readonly types.

Also, by marking both static defaultProps and state as readonly within the class, we eliminate the possibility of runtime errors arising from setting state as mentioned above.

Use Type Alias Instead of Interface for Declaring Props/State

While interface can be used, for consistency and clearness sake it is best to use type alias as there are cases where interface cannot work. For instance, in the previous example, we refactored our code to enable TypeScript’s type system to correctly infer readonly types by defining state type from implementation. We cannot use interface with this pattern as seen in the code below:

 
// works
type State = typeof initialState;
type Props = { someProps: string } & typeof defaultProps;

// throws error
interface State = typeof initialState;
interface Props = { someProps: string } & typeof defaultProps;


{% gist https://gist.github.com/lawrenceagles/74b89d92471cdadbba52065a87334549 %}

Also, we cannot extend interface with types created by unions and intersection, so in these cases, we would have to use type alias.

Don’t Use Method Declaration Within Interface/Type Alias

This ensures pattern consistency in our code as all members of type/inference are declared in the same way. Also, --strictFunctionTypes works only when comparing functions and does not apply to methods. You can get further explanation from this TS issue.

Consider the code below:

 
// Don't do
interface Counter {
  start(count:number) : string
  reset(): void
}
  
// Do
interface Counter {
  start: (count:number) => string
  reset: () => string
}


{% gist https://gist.github.com/lawrenceagles/63cc5145646f5f02c4e28e36bd8af926 %}

Don’t Use FunctionComponent<P>

Or its shorthand FC<P> to define a function component!

When using TypeScript with React, functional components can be written in two ways:

  1. As normal functions as seen in the code below:

 
type Props = { message: string };

const Greeting = ({ message }: Props) => <div>{message}</div>;


{% gist https://gist.github.com/lawrenceagles/276112f9e5ed21c69ba02ffec755a7e1 %}

  1. Using the React.FC or React.FunctionComponent as seen below:

 
import React, {FC} from "react";

type Props = { message: string };

const Greeting: FC<Props> = (props) => <div>{props}</div>;


{% gist https://gist.github.com/lawrenceagles/310dd40107547a3d3ed08ae782f767cf %}

Using FC provides some advantages such as type-checking and autocomplete for static properties like displayName, propTypes, and defaultProps. But it has a known issue of breaking defaultProps and other props: propTypes, contextTypes, displayName.

FC also provides an implicit type for children prop which also have known issues. Also, as discussed earlier a component API should be explicit so an implicit type for children prop is not the best.

Don’t Use the Constructor for Class Components

With the new class fields proposal, there is no need to use constructors in JavaScript classes anymore. Using constructors involves calling super() and passing props and this introduces unnecessary boilerplate plate and complexity.

We can write cleaner and more maintainable React class components by using class fields as seen below:

 
// Don't do
type State = {count: number}
type Props = {}

class Counter extends Component<Props, State> {
  constructor(props:Props){
      super(props);
      this.state = {count: 0}
  }
}

// Do
type State = {count: number}
type Props = {}

class Counter extends Component<Props, State> {
  state = {count: 0}
}


{% gist https://gist.github.com/lawrenceagles/ddd9bb947f736794db1d85d8b560f1f0 %}

In the code above, we see that using class fields involves less boilerplate and we don’t have to deal with the this variable.

Don’t Use Public Accessor Within Classes

Consider the code below:

 
import { Component } from "react"

class Friends extends Component {
  public fetchFriends () {}
  public render () {
    return // jsx blob
  }
}


{% gist https://gist.github.com/lawrenceagles/96e8c072ad43426b7306e77f1a462e4d %}

Since all members in a class are public by default and at runtime, there is no need to add extra boilerplate by explicitly using the public keyword. Instead, use the pattern below:

 
import { Component } from "react"

class Friends extends Component {
  fetchFriends () {}
  render () {
    return // jsx blob
  }
}


{% gist https://gist.github.com/lawrenceagles/87da70d3cef765b652e9532b98b52921 %}

Don’t Use Private Accessor Within Component Class

Consider the code below:

 
import {Component} from "react"

class Friends extends Component {
  private fetchProfileByID () {}
  
  render () {
    return // jsx blob
  }
}


{% gist https://gist.github.com/lawrenceagles/8e31307e752e5f6b93c901556bd1dfc1 %}

In the code above, the private accessor only makes the fetchProfileByID method private on compile-time since it it is simply a TypeScript emulation. However, at runtime, the fetchProfileByID method is still public.

There are different ways to make the properties/methods private in a JavaScript class private one is to use the underscore (_) naming convention as seen below:

 

import {Component} from "react"

class Friends extends Component {
  _fetchProfileByID () {}
  
  render () {
    return // jsx blob
  }
}


{% gist https://gist.github.com/lawrenceagles/d69d76d7c0c3c10c6e95af5354b0da2b %}

While this does not really make the fetchProfileByID method private it does a good job of communicating our intention to fellow developers that the specified method should be treated as a private method. Other techniques involve using weakmaps, symbols, and scoped variables.

But with the new ECMAScript class fields proposal we can do this easily and gracefully by using private fields as seen below:

 
import {Component} from "react"

class Friends extends Component {
  #fetchProfileByID () {}

  render () {
    return // jsx blob
  }
}


{% gist https://gist.github.com/lawrenceagles/594c426e763afe26c12badf60432831e %}

And TypeScript supports the new JavaScript syntax for private fields from version 3.8 and above.

Bonus: Don’t Use enum

Although enum is a reserved word in JavaScript, using enum is not a standard idiomatic JavaScript pattern.

But if you are coming from a language like C# or JAVA it might be very tempting to use enums. However, there are better patterns such as using compile type literals as seen below:

 
// Don't do this
enum Response {
  Successful,
  Failed,
  Pending
}

function fetchData (status: Response): void => {
    // some code.
}
  
// Do this
type Response = Sucessful | Failed | Pending

function fetchData (status: Response): void => {
    // some code.
}


{% gist https://gist.github.com/lawrenceagles/2dd0ecbd8d54ceae715feedc81d445cb %}

Conclusion

Using TypeScript no doubt adds a lot of extra boilerplate to your code but the benefit is more than worth it. Above are some best practices and idiomatic JavaScript patterns to apply when using React and TypeScript, to make your code cleaner and better.

TypeScript React (JavaScript library) Gist (computing) JavaScript Type system

Published at DZone with permission of Alex Omeyer. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • React, Angular, and Vue.js: What’s the Technical Difference?
  • The Cypress Edge: Next-Level Testing Strategies for React Developers
  • How to Build Scalable Mobile Apps With React Native: A Step-by-Step Guide
  • Mastering React App Configuration With Webpack

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!