ENGINEERING
2nd April 2019

Catching bugs in React using TypeScript

Runtime errors are pesky. In React, I personally start encountering them as the complexity of the application grows beyond what can be held in my mental picture.

In this case, I forgot to pass down content as prop into ListItem and got hit by an error message when running the web app. This is a fairly simple error, but I only found it out by running the actual code, which is the latest point the development process. Can I do better? Yes, by using TypeScript! TypeScript enhances modern JavaScript with static typings.

In the latter case, by strongly-typing the props accepted by the ListItem, the compiler was able to surface the error inside the editor itself. Hence I’ve just caught the same error in compile time!

To get started with TypeScript in React run:

npm install -g create-react-app
create-react-app my-react-app --typescript

I’ve found TypeScript to be very powerful at catching unsuspecting bugs in React. Let’s explore five ways TypeScript helps me in fighting off bugs while developer in React.

1. React Props and State

It is pretty common scenario that the internal state of a component is connected to an external component via an onChange function from its props:

class ListItem extends React.Component {
state = { text: '' };
render() {
return (
<div>
<input onChange={this.handleChange} />
<button onClick={this.handleClick}>Submit</button>
</div>
);
}
handleChange = (event) => {
this.setState({ text: event.currentTarget.value });
};
handleClick = () => {
this.props.onChange(this.state.text);
};
}

Looking at this, I may infer that the onChange is a function that takes a string and performs an update upstream, but I am unsure this is the case until runtime. Moreover, even if this is the case now, I can’t be sure the invariant is held in the future! Nothing is preventing the caller to change onChange to take in an event as opposed to a string.

Here’s the equivalent in TypeScript:

interface IProps {
onChange: (text: string) => void;
}
interface IState {
text: string;
}
class ListItem extends React.Component<IProps, IState> {
public readonly state = { text: '' };
public render() {
return (
<div>
<input onChange={this.handleChange} />
<button onClick={this.handleClick}>Submit</button>
</div>
);
}
public handleChange = (event: React.FormEvent<HTMLInputElement>) => {
this.setState({ text: event.currentTarget.value });
};
public handleClick = () => {
this.props.onChange(this.state.text);
};
}

No more guesswork, I know exactly that onChange takes a string and that the compiler will guard from the invariant changing in the future! Note in addition, I’ve also gained typings from the event passed into handleChange which guarantees the text update from the input will also be string.

2. React Context and React Hooks

I am a fan of React Context for state management. In fact, by using React Context with React Hooks I’ve no longer found the need to include React Redux inside my applications, but that's a topic for another day. Before React Hooks, I found myself doing a lot of nested render props:

import React from 'react';
import { StoreContext } from './storeContext';
import { DefaultContext } from './defaultContext';
function App() {
return (
<StoreContext.Provider>
<StoreContext.Consumer>
{({ onChange }) => (
<DefaultContext.Provider>
<DefaultContext.Consumer>
{({ defaultText }) => (
<ListItem onChange={onChange} defaultText={defaultText} />
)}
</DefaultContext.Consumer>
</DefaultContext.Provider>
)}
</StoreContext.Consumer>
</StoreContext.Provider>
);
}

This implies a level of hierarchy that isn’t necessarily there, it is verbose, and as before does nothing to ensure a stable invariance of the arguments.

With React Hooks:

import React, { useContext } from 'react';
import { StoreContext } from './storeContext';
import { DefaultContext } from './defaultContext';
function App() {
const storeContext = useContext(StoreContext);
const defaultContext = useContext(DefaultContext);
return (
<ListItem onChange={storeContext.onChange} defaultText={defaultContext.defaultText} />
);
}

The unnecessary hierarchy and verbosity are gone! But one more thing:

interface IStoreState {
onChange: (text: string) => void;
}
const StoreContext = React.createContext<IStoreState>({
onChange: (text: string) => {}
});
interface IDefaultState {
defaultText: string;
}
const DefaultContext = React.createContext<IDefaultState>({
defaultText: 'wu tang'
});

In TypeScript we can define the exact typing of each context and have it come through when using each hooks! This means no more render props nesting, clean and concise, and things are strongly typed.

3. Refactoring and Renaming

Refactors have always been a nightmare in my experience. Simply moving files around or renaming components required a thorough dissection of what was changed and what might need changing. However with the latest in VS Code, which combines module analysis and typings analysis both have become smooth as butter: Moving files:Renaming:

4. Libraries

Perhaps the most powerful feature of TypeScript is the vast typings library that the community behind it has built at DefinitelyTyped. Nearly every major React library has an equivalent typings library. Consequently, not only the code that I write is strongly typed, but also the code that I interact with is also strongly typed. 

import React from 'react';
import Button from '@material-ui/core/Button';
import { Theme, WithStyles, withStyles } from '@material-ui/core/styles';
const styles = (theme: Theme) => {
button: {
marginLeft: theme.spacing.unit
}
}
interface IProps {
defaultText: string;
onChange: (text: string) => void;
}
interface IState {
text: string;
}
class ListItem extends React.Component<IProps & WithStyles<typeof styles>, IState> {
public readonly state = { text: '' };
public render() {
return (
<div>
<input onChange={this.handleChange} />
<Button
color="primary"
className={this.props.classes.button}
onClick={this.handleClick}>
Submit
</Button>
</div>
);
}
public handleChange = (event: React.FormEvent<HTMLInputElement>) => {
this.setState({ text: event.currentTarget.value });
};
public handleClick = () => {
this.props.onChange(this.state.text);
};
}
export default withStyles(styles)(ListItem);

This is an example with the Material-UI framework. In this example, not only are my component props and state strongly typed as we walked through above, but also the Button imported from the library is strongly typed, and finally even the styling applied onto the component is strongly-typed.

W000T!

Come work with us!

If you’re a software engineer interested in helping us contextualize and categorize the world’s crypto data, we’re hiring. Check out our open engineering positions to find out more.