loading...
Cover image for Some reflections about React and TypeScript

Some reflections about React and TypeScript

rossanodan profile image Rossano D'Angelo ・2 min read

Since I am using TypeScript, I have some superpowers.

I can improve my code adding things like interfaces and return types. This won't change the behavior of the application but will make it easier to debug and more bug free.

Interfaces

I know that a route is an object structured as follows

{
  path: '/teams',
  sidebarName: 'Teams',
  component: Teams
},

The Routes module is used in different places within the application so it makes sense exporting an interface that defines the structure of each route.

In the Routes.ts file

...
export interface IRoute {
  path: string;
  sidebarName: string;
  component: React.ComponentType;
}
...
const Routes: IRoute[] = [
...

At this point is straight forward importing it from elsewhere.
In the App.tsx indeed I do

import Routes, { IRoute } from './Routes';
...
{Routes.map((route: IRoute) => (
  <Route exact path={route.path} key={route.path}>
    <route.component />
  </Route>
))}

Return types

When writing TypeScript is a good practice specifying functions' return types.

For example, a simple functional component like Home returns a JSX.Element

const Home: React.FC = (): JSX.Element => {
  return (
    <h1>Home</h1>
  );
};

A method like activeRoute returns a boolean

const activeRoute = (routeName: string): boolean => {
  return location.pathname === routeName ? true : false;
}

And so on.

Adding return types helps avoiding common misktakes and makes the application more testable.

props type

React Router DOM offers also the type of the props that a wrapped component receives. For example, I have withRouter(NavigationBar) receiveing some props form the HOC withRouter.

const NavigationBar: React.FC = (props: any) => {

The type of those props is RouteComponentProps.

import { NavLink, withRouter, RouteComponentProps } from 'react-router-dom';
...
const NavigationBar: React.FC<RouteComponentProps> = ({ history, location, match }: RouteComponentProps) => {

I also destructured the props to have direct access to its properties history, location and match.

Posted on by:

rossanodan profile

Rossano D'Angelo

@rossanodan

Application engineer at The LEGO Group. I like reading manga. All my opinions are belong to me.

Discussion

pic
Editor guide
 

The example with JSX.Element could be improved; since you already declare the type of the LHS to be a React.FC the RHS is fully determined. Rule of thumb is to be as specific as necessary, but not more. Same applies to the last example where the LHS already gives us all the required information.

Also your Routes.map is way too specific. Since the routes are already an array of IRoute the types of the map callback are fully determined.

One beautiful thing about TS is type inference and that the types "just flow". Make use of it!

 

Is it a norm to let TypeScript infer without specifying types in the code?

 

Not sure what you refer to, but yes - use type inference to let types flow. Constrain where necessary, e.g.,

function foo() {
  return {
    a: 'foo',
    b: 'bar',
  };
}

Now here TS infers that

declare function foo(): {
  a: string;
  b: string;
};

but maybe you want that foo always returns something like

interface FooReturn {
  a: 'foo' | 'bar';
  b: string;
  c?: boolean;
}

Now in the free return spec above that is not clear. Even worse, another function taking a parameter of type FooReturn will not work as the return is currently not compatible.

Sure, we could do:

function foo() {
  return {
    a: 'foo' as const,
    b: 'bar' as const,
  };
}

to improve the situation, but in this case decorating the return seems even better, because when we write the function we would already know that another property c is also possible (and its type).

So just use:

function foo(): FooReturn {
  return {
    a: 'foo',
    b: 'bar',
  };
}

Again, it all depends, but once types are given (as in many of the examples above) there is no need to re-specify. This is over-specifying, which should always be avoided (as in any potential refactoring this leads to a wave of unnecessary changes).

Thank you Florian for the detailed reply.

 
 

Yes, this article is the great justification for everybody who wants to stay with JS status quo. As I agree there is a trade-off, I never could agree that it is negative for TS.