TypeScript All the Things!
Tags: javascript, typescript, react
Published: 2020-12-10 10:09:16

---
title: TypeScript All the Things!
published: true
description: I'm building a social media network and collaboration tool based on mind maps, documenting my work in this series of blog posts
tags: javascript,typescript,react
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/4ah4h969o7u1ametz024.png
series: Nuffshell Coding Diary
---

*I'm building a social media network and collaboration tool based on mind maps, documenting my work in this series of blog posts. Follow me if you're interested in what I've learned along the way about building web apps with React, Tailwind CSS, Firebase, Apollo/GraphQL, three.js and TypeScript.*

# Today's Goal

In my [previous posts](https://dev.to/pahund/the-nuffshell-coding-diary-6ij), I've built a 3D mind map, using React and three.js. 

In the [last post](https://dev.to/pahund/local-state-management-with-apollo-client-14lf), I added local state management with Apollo Client. Since all the code examples I learned from were written in TypeScript, I decided to migrate my project to TypeScript, as well. Today, I'll convert all the JavaScript code for my 3D mind map that I've written so far to TypeScript.

# To Type or Not to Type

[TypeScript](https://www.typescriptlang.org) extends JavaScript by adding types, promising fewer bugs and a better developer experience.
 
I'm not going to lie, I've had my reservations with TypeScript. Still do, actually.

I don't like how it nudges you in the direction of object oriented programming, a programming style that in my opinion can do more harm than good if used incorrectly. Functional programming, on the other hand, which I love, can be a real pain to type properly.

I also read a lot of complaints on Twitter from developers who fiddle and fight the type system to do their bidding. There are blog posts from smart people whom I respect that say it's a hype that only got so big because it is backed by mighty Microsoft.

On the other hand, coming from Java programming, I know that types can go a long way to make programming easier and take mental load off a programmer writing code – the TypeScript compiler helps you with every keystroke, telling you what you variables can and cannot contain, what kind of arguments to pass to functions, where something might be null or undefined.

So I haven't made up my mind yet if, in the teaser picture above, I'm the boy with the trumpet or the girl holding her ears shut.

I have, however, made up my mind to use TypeScript for my 3D mind map side project, so let's get into the nitty gritty!

# Getting Started

If you're using [create-react-app](https://create-react-app.dev), like I do, getting started is quite easy. CRA has TypeScript “built in”. All you have to do is change the file name extension from a JavaScript module from *.js* to *.ts*, and boom – you have a TypeScript module.

TypeScript infers the types in the TS modules, so unlike Java, you don't have to write what type it is every time you create a variable. The TS compiler will just assume type [any](https://www.typescriptlang.org/docs/handbook/basic-types.html#any) when it can't figure out by itself what type something is.

![Warning about implicit any](https://dev-to-uploads.s3.amazonaws.com/i/sibtatou4bzisx3hesqp.png)

As you can see here, when the compiler runs in strict mode, it will complain about “implicit any” type in these cases – great! My goal is to not ever use “any” anywhere. I think only then using TypeScript really makes sense.

# Null Checking

One thing I've noticed while converting my code to TypeScript: the compiler warns me about something that may be null or undefined in cases where I just didn't bother with checking. I didn't bother checking because from my experience, I can rely on something to be defined/not null. The TS compiler of course can't judge from experience or gut feeling, it tries to help me and warn me.

Take the old JavaScript code of my *MindMap* React component, for example:

```javascript
function MindMap({ data }) {
  const divRef= createRef();
  useEffect(() => {
    renderMindMap(divRef.current, data);
  }, [divRef, data]);
  return <div ref={divRef} />;
}
```

It's just rendering a div to the DOM, then passing a reference to the DOM node to my `renderMindMap` function that creates the 3D model of the mind map.

Converted to TypeScript:

```typescript
interface Props {
  data: MindMapData;
}

export default function MindMap({ data }: Props) {
  const divRef: RefObject<HTMLDivElement> = createRef();
  useEffect(() => {
    renderMindMap(divRef.current, data);
  }, [divRef, data]);
  return <div ref={divRef} />;
}
```

I have to define an interface for the props to tell TypeScript what type of data can be passed to the component – great!

But what's this?

![MindMap component compiler warning](https://dev-to-uploads.s3.amazonaws.com/i/u4mkng7dlx4cgheqptw9.png)

TypeScript thinks `divRef.current` could be null, so I'm not allowed to pass it to the `renderMindMap` function, which expects a DOM element as first argument!

I add a null check to make the compiler happy:

```typescript
function MindMap({ data }: Props) {
  const divRef: RefObject<HTMLDivElement> = createRef();
  useEffect(() => {
    const div = divRef.current;
    if (!div) {
      console.error("Rendering the mind map div element failed");
      return;
    }
    renderMindMap(div, data);
  }, [divRef, data]);
  return <div ref={divRef} />;
}
```

I actually don't think the ref could ever be null, so did TypeScript, in this case, help me prevent a bug, or did it just force my to write extra code? 🤔 Debatable…

When I'm 100% sure I know better than the TypeScript compiler and something simply cannot be null or undefined, I can use `!` to override the null check:

```typescript
function MindMap({ data }: Props) {
  const divRef: RefObject<HTMLDivElement> = createRef();
  useEffect(() => {
    renderMindMap(divRef.current!, data);
  }, [divRef, data]);
  return <div ref={divRef} />;
}
```

❤️ Thanks [Daniel](https://dev.to/crabmusket) for pointing this out in the comments!

# Adding My Own Types

When including a library in your project, usually by installing an npm package, those libraries need to have type definitions if you want to use them properly with your TypeScript project.

Luckily, all the libraries I've included in my 3D mind map project so far do have types. It's great to see that TypeScript nowadays is already so widely supported! 👍🏻

There is only one dependency that doesn't have types, [three-trackballcontrols](three-trackballcontrols). I'm using this to be able to zoom, pan and rotate my model ([see previous post](https://dev.to/pahund/adding-trackball-controls-to-a-three-js-scene-with-sprites-48hp)).

So, what to do?

I have to add my own type definitions. Create-react-app comes with a type definition file [react-app-env.d.ts](https://codesandbox.io/s/step-22-typescript-pl4mr?file=/src/react-app-env.d.ts) that I can use to add my type definition:

```typescript
declare module 'three-trackballcontrols' {
  declare const TrackballControls: any;
  export default TrackballControls;
}
```

With this, I can at least import and use the library in my code without compiler warnings. Of course, it doesn't add any value, because I'm just saying the constructor for the `TrackballControls` object is a function that can accept any old arguments and returns who knows what.

“But Patrick, you said your goal is to not use *any* anywhere!” – yes, yes, I should really create a proper type definition here. Someday, somewhere, somehow… 😅

## Update!

There actually *is* a TypeScript version of the library, [three-trackballcontrols-ts](https://www.npmjs.com/package/three-trackballcontrols-ts).

When looking for a TS compatible version of an npm package, or for type definitions for a package that you can install separately, it is always a good idea to search on the [type search page](https://www.typescriptlang.org/dt/search?search=) of the official TypeScript website.

❤️ Thanks [stereobooster](https://dev.to/stereobooster) for pointing this out in the comments!

# CSS Modules

Another thing I had to do some research one is using CSS modules properly. I have one CSS file per React component, containing the styles for this particular component. I can import the CSS files in the TypeScript module thanks to some dark webpack magic. 

TypeScript is not happy about this:

![CSS module compiler warning](https://dev-to-uploads.s3.amazonaws.com/i/jgl8xfnpyip3m1gxky14.png)

To fix this, I add this custom type definition to my [react-app-env.d.ts](https://codesandbox.io/s/step-22-typescript-pl4mr?file=/src/react-app-env.d.ts) file:

```typescript
declare module '*.css' {
  interface IClassNames {
    [className: string]: string;
  }
  const classNames: IClassNames;
  export = classNames;
}
```

It's a good thing that TypeScript has been around for some time now and is widely used, so in cases like this, someone else already had the same problem long ago and fixed it. I can just copy my solutions off StackOverflow or GitHub.

# Enforcing Good Practices

One thing I like as I convert my project to TypeScript that the compiler calls me out in cases where I've used bad coding practices and forces me to do better.

Let me explain with one example:

*[renderMindMap.js](https://codesandbox.io/s/step-21-local-state-with-apollo-part-6-181dt?file=/src/renderMindMap.js)* (before converting)

```javascript
data.nodes = await Promise.all(
  data.nodes.map((node) =>
    renderToSprite(<MindMapNode label={node.name} level={node.level} />)
  )
);
const graph = new ThreeForceGraph().graphData(data);
graph.nodeThreeObject(({ sprite }) => sprite);
```

This code is preparing the nodes of my 3D mind map by pre-rendering them asynchronously. The `ThreeForceGraph` library has a method that allows me to pass custom objects for the graph nodes, which I'm using to pass the pre-rendered sprites.

What's wrong with this code? 

The object `data` was passed as a function argument. It contains all the data of my mind map. I'm blatantly mutating this object by adding the pre-rendered mind map nodes, for `ThreeForceGraph` to use. Mutating an object that is passed as an argument to a function makes this function impure and is a bad coding practice, indeed.

With TypeScript, I have to define a type for my mind map data. I tried defining the type of `data.node` so that it contains a sprite. However, when `ThreeForceGraph` passes this data to the `nodeThreeObject` callback function, the TypeScript compiler notices that there is a `sprite` property in there that, according to the type definition of `ThreeForceGraph` should not be there.

I fix this by creating a separate map of pre-rendered nodes and then access this map in `nodeThreeObject`:

*[renderMindMap.tsx](https://codesandbox.io/s/step-22-typescript-pl4mr?file=/src/features/mindMap/renderMindMap.tsx)* 

```typescript
const preRendered: Map<
  string | number | NodeObject | undefined,
  PreRendered
> = new Map();
await Promise.all(
  data.nodes.map(({ name, val, id }) =>
    renderToSprite(<MindMapNode label={name} level={val} />)
    .then((sprite) => 
      preRendered.set(id, { sprite, linkMaterial });
    )
  )
);
const graph = new ThreeForceGraph().graphData(data);
graph.nodeThreeObject(({ id }) => {
  const sprite = preRendered.get(id)?.sprite;
  if (!sprite) {
    console.error(`Error – no pre-rendered mind map node for ID ${id}`);
    return new THREE.Mesh(
      new THREE.BoxGeometry(),
      new THREE.MeshBasicMaterial({ color: 0xffffff })
    );
  }
  return sprite;
});
```

Note how, again, in this case I have to add a null check, even though I'm pretty sure that, after having pre-rendered a sprite for every ID, it cannot happen that the map will return null. Oh, well…

But it is a good thing that TypeScript makes me collect the pre-rendered sprites in a separate map instead of just adding them to the original data. “TypeScript made me a better developer.” 😂

# The Result

These were just a few things I've noticed while working with TypeScript. I hope you found them interesting. If you want to take a look at the whole project after conversion to TS, here's the code sandbox:

{% codesandbox u8rz7 %}

# To Be Continued…

I'm planning to turn my mind map into a social media network and collaboration tool and will continue to blog about my progress in follow-up articles. Stay tuned!