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.
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.
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.
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.
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.
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.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.