← Back to all posts

Updating React and TypeScript: The Path to a Better Developer Experience

Updating React and TypeScript: The Path to a Better Developer Experience

10 min read

Photo Credit: Allison & Rupert Photography

Introduction

Do you ever have those days where you wake up, start your work day and just think, how did I get into this mess?

I had one of those days. Mid way through a feature I was working on and the application said enough was enough. Downloading a new dependency was not going to be possible without upgrading to React 18. Now, if your project was up to date or you didn’t have many other dependencies, this wouldn’t be so much of an issue. Unfortunately, up until now, updating the dependencies kept getting pushed aside for developing features.

When I looked into this it was clear that the project was pretty far behind. One package was 3 major versions out of date! I started with getting the packages updated without conflicts which definitely took longer than I hoped.

React Router

Then it was time to update the router. We were using react-router-dom version 5, which as many of you well know, is quite a bit different to the newer version 6. We had a custom component that wrapped each react-router Route, checked a condition and either returned the component of the desired screen, or redirected the user. This looked like the following.

1routes.tsx
2  condition = authentication
3  redirect = /login
4		
5  - Router
6	- ProtectedRoute-1 path=two-path props=(condition, redirect, component-1)
7	- ProtectedRoute-2 path=three-path props=(condition, redirect, component-2)
8	- ProtectedRoute-3 path=four-path props=(condition, redirect, component-3)
9	- Splat-Route
10  - /Router
11
12
13protectedRoute.tsx
14  (conditon, redirect, component)
15		
16  if condition == false
17	redirect
18		
19    return
20    	- Route + component

The replacement code needed to take into account that any component nested inside the Router now needs to directly be a Route component from react-router-dom and not a wrapper like the ProtectedRoute above.

Thankfully, the silver lining on this is that with version 6 you can now have nested Route components.

So let’s dig into that for a second.

1- Parent 1
2    - Child 1
3    - Child 2
4		
5- Parent 2
6    - Child 3
7    - Child 4

If the above were folders and we wanted to access the child 2 folder, our route to that folder would be Parent1 -> Child 2. Similarly, with nested Routes, you could arrange that structure with your home route and all the nested children. Let's say you have a settings page, and in that page you have some tabs. You could prefix that grouping with settings and have all the tabs be the children of that parent route.

I'll show you that high level visual in a second, but first there's something else. You can pass layout components to the parent in that structure which will encapsulate all of the child routes with the condition, redirect and the outlet for the destination route. This was the key to both solving the protected route issue above and for tidying up the router component.

This ended up looking like the following:

1routes.tsx
2  condition = authentication
3  redirect = /login
4		
5  - Router
6    - Route path=/ layout=ProtectedRoute props=(condition, redirect)
7	  - Route 1 element=component-1 path=one-path
8  	  - Route 2 element=component-2 path=two-path
9  	  - Route 3 element=component-3 path=three-path 
10    - /Route
11    - Splat-Route
12  - /Router
13
14
15protectedRoute.tsx
16  (conditon, redirect, component)
17		
18  if condition == true
19    return Outlet
20				
21  else
22	return Redirect

This allowed me to tidy up our 30 or so routes into much more readable code which has greatly improved the DX just by itself. I am looking forward to version 7, which will encompass the merger between react-router-dom and remix run, which is currently what this portfolio website is built with.

TypeScript

So with the router now doing what it should and all nice and tidy, it was time to address the next hurdle. The glowing red of all the typescript errors that could light up a city skyline. Along with many others, TypeScript was a full major version behind and I was now updating from v4 to v5. Still, a step in the right direction it was time to face the music. Did I mention I also added Airbnb’s Eslint extension?

The eslint extension pulled up thousands of issues in our code base, thankfully eslint --fix solved a huge chunk of those, but the rest set me up for a long day.

There have been a ton of changes in TS version 5 (list here -> https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/), however, I was feeling pretty excited about having access to a lot of new feature I could start implementing into the app.

The return of React-Router changes

Not all of these were TS specific errors of course, some were just because packages changed their import naming, usually from default to named imports. A good one, again related to react-router-dom was the useHistory hook. This was now gone, replaced with the useNavigate hook. It’s slightly simpler with going from:

1import { useHistory } from 'react-router-dom';
2
3export const SomeComponent = () => {
4  history = useHistory();
5		
6  history.push('/some-route');
7	
8  ...component logic and return node
9}

To:

1import { useNavigate } from 'react-router-dom';
2
3export const SomeComponent = () => {
4  navigate = useNavigate();
5		
6  navigate('/some-route');
7	
8  ...component logic and return node
9}

A grand saving of one word per execution which added up once I had changed the wording of approximately a hundred uses of it.

Vite and Vitest

This project was created a few years ago by another team when it was still the norm to build applications with create-react-app (CRA). These days a developer might look at you sideways if you said you wanted to create an enterprise application using CRA and Webpack instead of something more DX focussed such as Vite. Thankfully upgrading to Vite from CRA is an easy and enjoyable DX unto itself. Vitest also substituted essentially 1-1 for the unit tests written with Jest and there was a noticeable performance gain in the execution of tests too. All in all a +1 for the experience overall.

Conclusion

I was not prepared for this but it taught me that staying up to date with dependencies is something not to be taken lightly, if that means pushing the importance a little more to management then it's worth it, because roadblocks like this have flow on effects and can blow out a timeline pretty quick.

The feeling I have right now as I get back into the half done feature, is one of pride, security and excitement for the better DX and new features of React 18.

Thank you for reading, if you have any thoughts about this article please feel free to contact me.