
Porting a React Front End to TypeScript - gary_bernhardt
https://www.executeprogram.com/blog/porting-a-react-frontend-to-typescript
======
scottfr
You can get a lot of the benefits of Typescript in VSCode without leaving
JavaScript behind by using JSDoc.

Typescript will pick up the JSDoc type annotations and use them to type the
code. This can be a great option when typing an existing project.

Docs: [https://www.typescriptlang.org/docs/handbook/type-
checking-j...](https://www.typescriptlang.org/docs/handbook/type-checking-
javascript-files.html#supported-jsdoc)

~~~
hombre_fatal
Vanilla VSCode also has surprisingly good code inference for
Javascript/Typescript.

Recently, Typescript was complaining about an implicit `any` even though the
VSCode tooltip clearly could infer the actual type (it confusingly was showing
both cases in the tooltip). I finally realized that VSCode's built-in
inference worked here but Typescript didn't work at all because I forgot I was
importing a .js file rather than a .ts file.

VSCode for course also downloads @types/* files in the background even in
vanilla JS projects which is super helpful because you get effortless
intellisense. That it does this kind of stuff out of the box is the sort of
reason why I've lost my Vim/Emacs fanaticism over the years.

~~~
DanRosenwasser
VS Code's JS support is actually still powered by TypeScript. I'm not 100%
sure exactly what you were experiencing, but it's intentional that VS and VS
Code automatically download types when powering JavaScript contexts because we
try to make things "just work" for JS users.

~~~
tomnipotent
> intentional that VS and VS Code automatically download types

Are these coming from DefinitelyTyped?

~~~
WorldMaker
Indirectly, sometimes. They come from npm, but the largest chunk of type
packages for libraries that do not provide their own types on npm are serviced
from DefinitelyTyped (via the `@types` organization and its long tail of
shadow packages).

------
sefrost
I’ve found this React + TypeScript cheat sheet extremely helpful while
recently porting a React codebase to TypeScript. (To the point it’s one of
only four bookmarks on my browsers bookmark bar.)

Perhaps others here will find it useful as well.

[https://github.com/typescript-cheatsheets/react-
typescript-c...](https://github.com/typescript-cheatsheets/react-typescript-
cheatsheet#reacttypescript-cheatsheets)

~~~
eliseumds
Same here, combined with a bunch of curious looks at the DefinitelyTyped
repository pull requests:
[https://github.com/DefinitelyTyped/DefinitelyTyped/pulls](https://github.com/DefinitelyTyped/DefinitelyTyped/pulls)

------
stgewehr
It seems that I fundamentally misunderstand the benefits of TypeScript. I
found myself spending hours and hours on reading the type errors stacktraces
and adding types for libraries. Writing TS code takes 2x time than simple
JS... and it is extremely painful experience. In comparison to many old plain
JS projects I did - it simply doesn’t make any sense, the TS solves problems
which I haven’t experienced at all.

~~~
sunaurus
Writing statically typed code will always be slower than writing dynamically
typed code.

People don't use static typing for writing code faster, they use it for code
that can be READ (and understood) much faster, especially by somebody who
doesn't work with that specific piece of code every day.

I think it's generally agreed that in most situations, reading code is much
more common than writing code, so static typing really make sense.

~~~
winter_blue
_> they use it for code that can be READ (and understood) much faster_

You nailed it.

I agree, this is _the biggest_ benefit of static types.

I worked at on a several hundred thousand line JS code base at a company, and
people were passing objects around, and it was extremely painful to trace code
and figure what the structure of the object was. I had to set a breakpoint in
the browser, and inspect the object during runtime. Moreover, some fields
would randomly be missing, because the field wasn't necessary for that
instance of the object. It was infuriatingly maddening.

I ended up spending a lot of time adding Flow types to it, of the form:

    
    
        type Foobar = {
            someField1: string,
            someField2: number,
            someField3: Baz,
            fieldThatIsNotAlwaysThere: ?Qux
            ...
        }
    

The untyped state of the code base gave me an extremely hard to resist to add
Flow static types wherever I could. I also added a step to the pipeline (with
my manager's support) that would break the pipeline and make it impossible to
merge new code, if it didn't have Flow static types (and I used flow-coverage
to make sure the "any" keyword wasn't being used excessively to side step Flow
type checking). I was told by some of my teammates to stop forcing types down
their throat. I eventually spent so much reworking large parts of the code
base, and adding static types to it that my other work suffered (and I wasn't
putting as much time as I should have into it), that I was told to stop
spending so much time on adding Flow static types. But it was hard to resist
the temptation. When I had to implement a new feature / change a file, I would
add Flow types to it, and then be drawn to adding types to the various other
files that it connects to (imports from, passes data to, etc). They fired me
in the end, for that (not prioritizing the things I was supposed to do well
enough) and other reasons (was going through relationship issues and
eventually a bad breakup, which caused associated psychological/personal/self-
care issues).

I've learned my lesson. Dynamic types aren't my cup of tea, and I find
dynamically typed code to be repulsive and quite nauseating.

~~~
MaxBarraclough
> some fields would randomly be missing, because the field wasn't necessary
> for that instance of the object

This problem can occur in statically-typed languages too, such as if you have
a class to model your entity, but in this instance you only need certain
fields to be hydrated. The other fields may be set to null.

This might only a problem if the half-populated instance is 'leaked' to
somewhere that expects a fully hydrated instance. Thinking about it though,
it's possible that _null_ could also be used to represent a null held on the
database, rather than _not hydrated_.

The ideal solution would I suppose be to roll your own type for this semi-
hydrated data, but that's not always possible/convenient.

Example: [https://github.com/microsoftgraph/msgraph-sdk-
dotnet/blob/de...](https://github.com/microsoftgraph/msgraph-sdk-
dotnet/blob/dev/docs/overview.md#query-options)

~~~
xsmasher
Typescript makes it convenient to roll your own type-

`let draft : Partial<Foo>;` says that 'draft' is shaped like a Foo, EXCEPT all
of the fields are optional.

If you try to access a field on draft that doesn't exist on Foo, it's a ts
error. If you try to pass draft to a function that takes a Foo, it's a type
mismatch.

------
DanRosenwasser
Hey all, I work on the TypeScript team. I've been super happy to read some of
the posts about the migration on Execute Program. If anyone has any feedback
or questions on TS, I can also try to answer them.

~~~
brlewis
Noticing the time when you posted your comment -- how big is the TypeScript
team, and how many time zones does it cross?

~~~
DanRosenwasser
In total there's roughly 20 people, more or less split between core
compiler/language service and VS editing scenarios for JS & TS. We also work
pretty closely with the VS Code team on their integration.

> Noticing the time when you posted your comment

Uh, I am just kind of a night owl. Most of our team members work out of
Redmond, WA, and our distributed members are either on the East or West coast
of the US.

------
joelbluminator
I'm not saying I never ever run into bugs due to dynamic types such as
method/variable typos, I just think it's much more rare than people make it
out to be. If that sort of thing happens a lot in a project you basically have
very little tests. I'm confident in saying it happens to me maybe once in a
few months - and that's working in a super dynamic environment of Rails. The
Null error does happen, but that happens in java as well.

~~~
com2kid
Not having to go to the code and investigate what the types of am object's
fields are. That alone can make TS worth it.

Heck just getting auto-complete on an object's members.

~~~
hombre_fatal
Also any sort of combinator usage or functional programming reminds me of why
static typing is so useful.

A few layers of higher order functions / combinators and I don't remember if I
have `value` or a `{ value: value }` container (like map vs flatMap). Or which
stage of the curry I'm at. Oh right, I'm at foo(a, _, c) but thought I was at
foo(a, b, _).

If you can't even remember the last time you've encountered a runtime error,
you are by my definition writing trivial code or not writing much code at all.

------
prmph
I love TypeScript, but have encountered a situation where I simply could not
compile my code anymore because it started causing an internal error in the
compiler itself.

Fortunately, I work as a team lead, and this was on an experimental branch, so
I was able to hand off bits of code relating to architectural patterns I was
evaluating to my team to continue work on those.

But I simply could not proceed with my code as is, a very frustrating
experience to say the least. How is this even possible?

~~~
mayank
> But I simply could not proceed with my code as is, a very frustrating
> experience to say the least. How is this even possible?

As a team lead, I'm sure you know...bugs happen. What sort of internal error
was it causing? Something specific and spelled out, or a generic uncaught
exception?

~~~
prmph
.../node_modules/typescript/lib/tsc.js:78602 throw e; ^

Error: Debug Failure. at Object.assertDefined
(/media/psf/Code/Hypothesize/node_modules/typescript/lib/tsc.js:1690:24) at
/media/psf/Code/Hypothesize/node_modules/typescript/lib/tsc.js:13175:89 at
String.replace (<anonymous>) at formatStringFromArgs
(/media/psf/Code/Hypothesize/node_modules/typescript/lib/tsc.js:13175:21) at
Object.createFileDiagnostic
(/media/psf/Code/Hypothesize/node_modules/typescript/lib/tsc.js:13191:20) at
createDiagnosticForNodeInSourceFile
(/media/psf/Code/Hypothesize/node_modules/typescript/lib/tsc.js:7770:19) at
Object.createDiagnosticForNode
(/media/psf/Code/Hypothesize/node_modules/typescript/lib/tsc.js:7760:16) at
handleSymbolAccessibilityError
(/media/psf/Code/Hypothesize/node_modules/typescript/lib/tsc.js:71556:50) at
checkEntityNameVisibility
(/media/psf/Code/Hypothesize/node_modules/typescript/lib/tsc.js:71929:13) at
visitDeclarationSubtree
(/media/psf/Code/Hypothesize/node_modules/typescript/lib/tsc.js:72066:29)

------
czei002
Regarding the previous backend related article and code sharing between
backend and frontend, how does your project setup look like?

For example, does the code get compiled using a bundler like webpack? if yes
how do you solve the import '../../../../../shared.ts' problem? Or do you use
node modules in a monorepo? Compared to other languages I found these
questions surprisingly difficult to answer and solutions often quite
cumbersome, e.g. if I want to share one small file in my project I don't want
to maintain a public npm module for it...

~~~
gary_bernhardt
(I wrote the article.)

We use a single git repo with no npm packages defined, other than the
package.json in the root because we have to put dependencies etc. in there.
The directory structure for our source code is dead simple:

    
    
      src/server
      src/client
      src/common
    

For example, the API endpoint definitions are common code, so you'll see stuff
like this in client code that uses the API:

    
    
      import * as quizApi from "../../common/api/pages/quiz"
    

The ".."s are annoying, but working around them isn't worth the effort. Even
when we heavily reorganize the file organization, it only ends up taking a few
minutes to mechanically update these imports with a vim macro, or even with
sed if it's a perfectly mechanical change.

We run two copies of tsc: one for the client and one for the server, building
to build/server and build/client. That results in a weird build directory
structure:

    
    
      src/server/db.js
      src/client/app.tsx
      src/common/endpoints.ts
      
      build/server/server/db.js
      build/server/common/endpoints.ts
      
      build/client/client/app.tsx
      build/client/common/endpoints.ts
    

If you need separate build settings for client and server, this weirdness is
going to show up one way or another. However, we only introduced this build
separation in the last month or so. For the first 1.5 years of the project, we
got away with a single tsc process and tsconfig.json, with no build separation
at all between client and server. If anyone who's newer to TS reads this, I'd
encourage looking for simple solutions like that; you can get to the weirder
stuff down the road if you need it (and you may never need it!)

~~~
czei002
Thanks for sharing! We actually tried the same approach but we have a couple
of more micro services relying on the same shared code. This has some
additional complications, e.g. managing all dependencies for all services in
one package.json is a bit messy and preparing a pkg for deployment gets a bit
more complicated...

~~~
gary_bernhardt
Ahh, if you're taking the microservice path then you're going to have to fight
all of that complexity. So far we've stuck to the constraints that I set at
the beginning of the project: vanilla Heroku web dynos; exactly one Heroku
worker dyno (never 0 or 2); and Postgres is the only data store (no queues
etc.)

------
LAMike
The question is, is it worth it?

I'm starting to feel behind the curve and I want to learn new stacks like
Next.js, Typescript and GraphQL. Anyone else feeling this way or have already
gone through that learning curve?

~~~
kilburn
I have transitioned my entire team (comprising people ranging from fresh-out-
of-a-bootcamp to senior 20-years-in-the-web).

Next.js and GraphQL are interesting technologies, but I would say they will
eventually fade. They do solve some problems, but they introduce new (hard to
fix) ones.

Typescript is here to stay. The improvement it brings to the development
experience cannot be understated.

It helps the inexperienced to avoid lots of (minor) bugs and providing much
more useful autocompletion/inline docs than any javascript analyzer can.

For the experienced people, it is an invaluable tool when the time for
largeish refactorings comes. For the first time you can "follow-the-trail-of-
errors refactor" in frontend-land.

~~~
shriek
This question probably varies on who I'm asking but how long does the
transpiling process take for you on a, let's say, fairly large project?

~~~
kilburn
We were already using Webpack + babel. Adding transpilation there is fairly
inexpensive (done by babel, without type checking).

Type checking did roughly double our build time. Even then, we do run type
checking during builds because we prefer the added safety even with this extra
time.

Developers run with an incremental checking watcher. It does add a significant
tax, and takes a few seconds after saving in some cases (3-4). We would love
that delay to go away, but it is a cost we are more than willing to bear if
the alternative is not having type checking at all.

------
ludamad
One critical thing for TypeScript's success that I wish for the Lua community
is that it paved a roadmap for typing a huge chunk of the JS ecosystem.
Porting to TypeScript can be entirely incremental, and you usually have a
wealth of types coming in from dependencies. The UX here is going to be one of
the best as far as ports go, as usually porting feels negative value up front

------
Kiro
I want to start using TS for my Node service but ts-node seems janky and the
compability with ndb (which I use all the time) seems bad.

~~~
DanRosenwasser
Anything specific about ts-node that doesn't work or that stuck out as janky?

~~~
Kiro
I keep reading people saying you should use tsc + regular Node instead which
is enough to make me worried but haven't really evaluated it myself.

------
conmigo
I've never increased my productivity by using Typescript, although I really
tried very hard to like it. It just keeps bugging me. It's a constant stream
of interruptions that force me to please the TS compiler, compared to having a
rare type issue a once in a while in plain JS.

Besides that, I've never been a Microsoft fanboy(to say the least) and it's
worrying me that almost the entire JS eco system falls in the hands of that
company; Github, VSCode, Typescript, NPM, etc.. Am I alone in this?

There is so much good stuff in the JS world, but we seem to adhere to a few
companies and a few systems more and more. Being a 'Javascript' developer
today only has very little to do with Javascript. It's about React, Redux,
Hooks, ESLint, Prettier, Typescript, etc.. Oh, and don't forget to do it
Agile, another joy and productivity killer. And when you don't agree with this
stack you're either not so smart or still need to learn to 'understand' it.

~~~
sli
Your comment is pretty much exactly the reason why I'm moving to Elm and
PureScript as much as possible. As a specific point, function types for redux-
thunk are just an absolutely miserable mess and that alone moved me to redux-
saga. But personally I just want to dump the whole wretched thing. I'm glad
people are waking up to this, because I felt like the odd one out disliking
Typescript and preferring Javascript.

Typescript in my experience has felt like a collection of hacks more than a
well thought out expansion of Javascript. Additionally, it concerns me that
official solutions to some problems (e.g. iterating on an enum) require you to
write around the code that the compiler will generate. That does not fill me
with confidence at all.

As a personal point of contention, I really dislike that the types are useless
beyond design-time. But since they don't want to go outside the EMCA spec,
we'll likely not see anything like pattern matching on type or type-level
destructuring anytime soon.

As it stands, some trivial things can become a nightmare very quickly. Maybe
I'm just used to the ease at which ML-family languages can express some
concepts.

~~~
acemarke
What specific concerns did you have with the redux-thunk types?

