Austin Roy Omondi Live long and prosper 👌

How to build React components for codebases that use JavaScript and TypeScript

5 min read 1614

TypeScript has been gaining a lot of popularity recently due to how it helps improve the development experience by catching errors and suggesting fixes prior to running the code. For this reason, TypeScript goes a long way towards preventing any runtime errors and reducing bugs.

TypeScript does this by extending JavaScript to add types and perform type checking during development, as a result, all type errors must be fixed prior to running the code, similar to languages such as Elm.

TypeScript is fairly great and in some cases, you may want to plug it into a codebase that already utilizes JavaScript, this can be used hand in hand with your JavaScript code or to gradually migrate your codebase to JavaScript. In this article, we’ll be looking at how we can use TypeScript with React and built components that are usable within both TypeScript and JavaScript files. To do this we’ll be building a simple display component that takes in some data and shows it to the user.

Creating your application

In order to save time on the configuration of our developer environment, we will be bootstrapping our application with create-react-app and fortunately for us, it also happens to have a TypeScript template that we can use to quickly get up and running.

If you don’t have create-react-app installed, install it by running:

npm install -g create-react-app

With create-react-app already installed, create your application by running:

yarn create react-app my-app --template typescript

Get into your app’s directory and run using the yarn start command in your terminal.

We’ll be having some minimal routing in our application using react-router, so we will need to install some dependencies:

yarn add react-router-dom @types/react-router-dom

This will install two packages, react-router-dom provides the DOM bindings for react-router whereas @types/react-router-dom contains the type definitions for react-router-dom .

Now that we have the boilerplate for our application setup, create two new directories in your src folder, pages which will hold the two simple pages we will be having on our application and DisplayData which will house our component.

We made a custom demo for .
No really. Click here to check it out.

Routing and pages

Each of these pages will use the same display component that we will be creating, with one of the pages implemented with JavaScript and the other implemented in TypeScript.

Inside the pages folder, create two files, WithTS.tsx and WithoutTS.jsx both these files will contain functional components that just pass some data into our reusable display component. Our JavaScript page will contain a component called DisplayWithoutTS which we’ll place in the WithoutTS.jsx and it will look like this:

import React from 'react'
import { DisplayData } from '../DisplayData'
const DisplayWithoutTS = () => {
    const info = {
        name: 'Anakin Skywalker',
        alias: [ 'Darth Vader', 'The Chosen One'],
        powers: ['Force Push', 'Force Pull', 'Force Vision'],
        rating: 10
      }
    return <DisplayData data={info}/>
}
export { DisplayWithoutTS }

Our TypeScript implementation, a DisplayWithTS component, will be in the WithTs.tsx file and contain this code:

import React from 'react'
import { DisplayData } from '../DisplayData'
const DisplayWithTS = () => {
    const info = {
        name: 'Sheev Palpatine',
        alias: [ 'Chancellor Palpatine', 'Emperor Palpatine', 'Darth Sidious'],
        powers: ['Force Push', 'Force Pull', 'Force Vision', 'Force Lightning', 'Sith Storm', 'Essense Transfer'],
        rating: 10
      }
    return <DisplayData data={info}/>
}
export { DisplayWithTS }

Now that we have our base pages set up, our next step is to set up our routing. Since we only have two routes, we’ll keep it simple and edit our App.tsx file:

import React from 'react';
import './App.css';
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import  { DisplayWithoutTS } from './pages/WithoutTS'
import { DisplayWithTS } from './pages/WithTS'

function App() {
  return (
    <div className="App">
      <div>
        <a href="/">With JS</a>
        <br/>
        <a href="/withts">With TS</a>
      </div>
      <br/><br/>
      <BrowserRouter>
        <Switch>
          <Route exact path='/' component={DisplayWithoutTS}/>
          <Route exact path='/withts' component={DisplayWithTS}/>
          <Route exact path='*' >
            <div>
              404 Page Not Found
            </div>
          </Route>
        </Switch>
      </BrowserRouter>
    </div>
  );
}
export default App;

We have two links before the BrowserRouter component, these will let us switch between the two pages in our app. We used Switch to handle routing and handled 404 errors by matching them to the * path to display the error message for all unaccounted paths.

Building our reusable component

Before we set up our TypeScript compatibility, we need to build our component first. In theDisplayData directory, create two files, DisplayData.jsx and index.ts .

Let’s create our display component inside DisplayData.jsx :

import React from 'react'
import {shape, string, number, arrayOf} from 'prop-types'

const DisplayData = ({data}) => {
    return (
        <div>
            <div>Name: {data.name}</div>
            <div>Alias: {data.alias.join(', ')}</div>
            <div>Powers: {data.powers.join(', ')}</div>
            <div>Rating: {data.rating}</div>
        </div>
    )
}
DisplayData.propTypes = {
    data: shape({
        name: string,
        alias: arrayOf(string),
        powers: arrayOf(string),
        rating: number,
    })
}

DisplayData.defaultProps = {
    data: {
        name: '',
        alias: [],
        powers: [],
        rating: null,
    }
}
export { DisplayData }

Our component takes in a data prop which it uses to fill some information about a fictional character and display it to the user. We carry out prop validation with prop-types and, in this case, our component receives a single prop which is an object. We use shape to describe an object type in our prop validation. We then proceed to fill in the types of the contents of our object such as string and number for text strings and numerical values respectively. For the data that comes in as an array, we use arrayOf to define the contents of the array, this is great as it provides more specificity than simply declaring the data using the array type.

We then export this component inside index.ts to make it available for use within other files.

Adding TypeScript compatibility

Now that we have our component set up and working, we need to add some TypeScript configuration in order to make sure type checking is handled by TypeScript when it is used within a TypeScript file (one with a .ts or .tsx extension). In order to make use of TypeScript’s type checking ability, we need to be familiar with the concept of Types and Interfaces, you can check out this great article covering both to help you get up to speed. Types basically specify the format of the various pieces of data we are passing in. Interfaces allow us to shape the values as well as fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project. We also need to be familiar with modules that allow us to encapsulate our added TypeScript into the existing React component and allow it to carry out type checking when used in a TypeScript file.

Incorporating TypeScript into the component is as easy as adding the following code to our index.ts file in the component folder:

interface DisplayDataProps {
    data : {
        name: String,
        alias: String[],
        powers: String[],
        rating: Number,
    }
}
declare module '.'{
    export const DisplayData: React.FC<DisplayDataProps>
}

We have our interface declared as DisplayDataProps , the type definitions within our interface are very similar to the prop validation we have with prop-types that we have within our component but much cleaner. for object types, we simply map the object as it will be structured. Other basic types will be defined as they were with prop-types with the only difference being that the type name is capitalized, i.e. string will be String and number will be Number, etc.

You may also notice our type definition foralias and powers which are both arrays of strings and a little different from what we have in our validation with prop-types, rather than arrayOf(string) we have String[] to declare an array of strings. This is not only cleaner than the prop-types implementation but it also automatically returns an empty list when the value is undefined, this can come in handy as it provides automatic default props to a component.

Once our interface has been defined we need to declare the component as a TypeScript module, in order to inform TypeScript to use the defined interface when the component is imported into a .ts or .tsx file. We defined our DisplayData component within the declared module using React.FC (React.FunctionalComponent in full) to make its return type more explicitly and provide type checking and autocomplete for static properties like displayName, propTypes, and defaultProps. You can find more information about this on the React TypeScript cheatsheet.

And that’s it, we’re good to go! Since we already did all the prior setup all we have to do is rerun our app. Switching between the routes should display the data as fed to the application by the two pages as shown in the demo below.

You can play around with the type validations to make sure everything is being picked up as expected, changing any of them should break the application.

Conclusion

You are now equipped with what you need to make your component usable in codebases that have either JavaScript or TypeScript files. This can come in handy in many cases, be it migrating your codebase or building a compatible component library. TypeScript’s type checking comes in handy in development and is definitely something that makes TypeScript worth picking up. If you wish to take a look at the code from this application you can check out this GitHub repo.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Austin Roy Omondi Live long and prosper 👌

Leave a Reply