Bad Coding Practices Series: TS Trojan Horse Union Types

Jeremy Tong
2 min readAug 1, 2023

In this series, I’ll share some of the common anti-patterns I see in ReactJS code.

In React, you often need to create JS objects to handle application state management. These could be params to async function callbacks, loading + error states, or UI component states. These objects are passed around, and shared across numerous React components.

When used well, Typescript works with the developer. Since Javascript uses functional programming patterns, Typescript not only provides type safety, but can importantly enforce object-oriented paradigms. However, when a developer has trouble wrangling Typescript, it’s usually an indication of a lack of understanding in proper coding patterns.

One of the most common examples of this, I’m terming Trojan Horse. It’s when you extend types not through inheritance, but rather union types, where there is a primary type and then other types that are snuck into the picture, and then dispatched accordingly by the offending object.

type MyErrorState = { errorMessage: string }
type IntendedObject = Record<string, string>
type IntentedObjectOrErrorState = IntendedObject | MyErrorState

function KeyValueRenderer({ input }: { input: IntentedObjectOrErrorState }) {
if ((input as MyErrorState).errorMessage) {
return <div>{(input as MyErrorState).errorMessage}</div>
}

return (input as Record<string, string>).entries().map([k,v] => <div>
{key}: {value}
</div>)
}

const getASimpleIntendedObject = (key?: string, value?: string) => {
if (!key || !value) {
//Make a naive MyErrorState, instead of properly throwing an error
return { message: "No key or value" }
}
return { [key]: value }
}

The reasoning of using this anti-pattern is often to inject a boundary handler into a component (in this case, an error handler). The flaws in this design are numerous, including the need for subsequent casting, low visibility to other devs, edge-case issues, and indication of another improper pattern to construct such an object (i.e. getASimpleIntendedObject()).

In the case above, I can see the developer’s intention, if they only knew about React Error Boundaries.

Even in its most innocuous form, you must be careful that the two types of values don’t yield different results. This is a frustrating bug to deal with since the union type is close and can be used often interchangeably.

type StringOrNumber = string | number

const x: StringOrNumber = "6";

function someLaterFunction(numbersToMatch: number[]) {
return numbersToMatch.includes(x)
}

//returns false because "6" !== 6
someLaterFunction([1,2,3,4,5,6])

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Jeremy Tong
Jeremy Tong

Written by Jeremy Tong

Startup-Focused Software Developer

No responses yet

Write a response