TypeScript & React: Improve your codebase using Types

TypeScript Benefits

TypeScript has been around for quite some time. This renown type checking language from Microsoft is a subset of JavaScript that introduces type checking to your code at compile time.

In short, TypeScript gives the feeling of a typed language while compiling to JavaScript behind the scenes.

If you’ve never heard of TypeScript consider watching this great talk by CodingTech:

While Angular comes packaged with TypeScript by default, it is less common to use TypeScript in combination with React. This is because React as library does not have types by default.

Similar to C# and other object oriented languages Typescript essentially makes JavaScript code safer. It ensures there is no ambiguity in the types of data used in an application or project.

For example, if you are expecting an array of strings to be passed into your function this can be ensured at compile time.

function outputText (textCollection : string[]) {
      textCollection.forEach(text => {
       return text
 }) } 
 const text : string[] = ["one", "two", "three"];
 outputText(text);

In this basic example of TypeScript implementation the parameters of the outputText function have ensured that it is receiving an array containing only string values.

In this post I  want to highlight how TypeScript can be used in combination with React quite effectively. It can make your React codebase more predictable to follow and reduce margin for error for a project’s expected data types.

Creating a project

Thankfully, setting up a React application with types is made incredibly simple by repos such as create-react-app-typescript.

In your terminal use the commands:

npm install -g create-react-app
create-react-app my-app --scripts-version=react-scripts-ts
cd my-app/
npm start

Any file with the extension .ts or .tsx(TypeScript + JSX) will now be able to use TypeScript syntax.

Props & State

There is a unique implementation of TypeScript in combination with React using Props and State.

Interestingly, we can define the types for props and state outside our React component.

export type State = {
  loading: boolean;
  isAuthenticated: boolean;
}

export type Props = {
  error: { errorHappened: boolean; errorMsg: string };
  textCollection: string[];
}

The Props and State types have been predefined here and exist in the the context of the file they are created in. This ensures that the values of these types should only be what we expect from our definitions.

These types are passed into a React Component like so:

class Results extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props)
    this.state = { loading: true, isAuthenticated: false }
  }

  render(){
    return (

   ) } }

At this point, in the constructor of the React component the state is initialized with default values:

 this.state = { loading: true, isAuthenticated: false }

So far so good, we have typed versions of State and Props. At any point where that component is used, the props sent down to it must match the types that have been defined.

Lets say the application has data about some sort of user transaction. This can be typed and defined in the state as an array of strings.

export type State = {
  loading: boolean;
  transactions: string[];
  isAuthenticated: boolean
}

Nothing too complicated here.

But how can we define a prop callback to send data up the component to its Higher Order Component?

Well, if there is data to be sent back up we can just define the parameters and define the callback like so:

export type Props = {
  onSaveTransaction: (transactions: string[]) => void
}

Notice how the onSaveTransaction callback is defined using an ES6 arrow function and using the type of void because its not returning anything.

In the render method of the component a button event can be wired up to interact with the callback in the props.

 render() {
    return (
      
    )
  }
 private onBtnClicked() {
    const transactions: string[] = this.state.transactions
    this.props.onSaveTransactions(transactions)
  }

There will be a type error before compile time if the right type of data hasn’t been sent into the onSaveTransactions callback.

Again, TypeScript is helping us here as the type of data flowing from the state to the callback prop will be what is expected.

However, what if the transaction data is something more than a arbitrary array of strings? Well, this is where we can define Interfaces.

Interfaces

An Interface in TypeScript creates a view model that allows the mapping of data on screen to use expected object type.

An Interface can dictate what properties a data structure should have. But it becomes extremely helpful if we know what type of data we will be receiving, for example from an API.

We can define an interface externally or locally inside the file

   export interface Transaction {
        id: number,
        coinType: string;
        currency: string;
        date: number;
        profit: boolean;
    }

Its as simple as that. The interface is now ready to be used for all data that uses that data properties of a transaction.

When we define an object as a Transaction our local Intellisense will reconfigure this and give you access to all its properties in a preview window.

Optional Properties

There are plenty of cases in a React application where its not necessary to send a specific piece of data.

If a function exists that is called in two different places one might want to send an optional parameter with it.

For example, if the application was tracking the changing of a textarea like so:

private handleTextArea(text, clearText?: boolean){

    if (clearText)
     this.setState(text: "")

     else {
        this.setState(text: "")
     }

  }
render() {
    return (
Proudly published with Gatsby