Image credit @ellladee
This week I have been working on a React app using Typescript and I've made a few discoveries that were very useful. This is one of my first projects using Typescript and so far I don't want to go back. Some of these discoveries may be common knowledge but for a Typescript novice, they are very useful for writing better code. For me at least. So without further ado, let's get into it!
Only allow specific keys on an object
This is quite useful when you want to limit the keys that can be added to an object. For example, allowing another dev to pass functions that should be used as event listeners. In that situation you only want the dev to pass vaild event listeners to avoid nasty errors.
type TListenerTypes = "onload" | "progress" | "error"
type TListeners = {
[k in TListenerTypes]: Function
}
// Passes!
const listenerObj: TListeners = {
onload: () => {}
}
// Error
const errorObj: TListeners = {
a: "something", // wrong type
progress: () => {},
d: 10 // not in objectKeys type
}
// Improvement added by this comment (https://dev.to/theodesp/comment/bd1k)
type TListenerTypes = "onload" | "progress" | "error"
const x: Record<TListenerTypes, Function> = {
a: "something", // wrong type
progress: () => {},
d: 10 // wrong type
};
Categorising Storybook Stories
In the project I am working on, we are using storybook to test our components. Once you've added a few stories, you start wishing for a way to categorise these into relevant groupings. Luckily there is a solution for this! As a side note, I cannot recommend storybook enough. It is SUPER useful for visually testing components independently. With the power of addons you can do accessibility checking, light/dark mode testing etc.
// uncategorised
storiesOf("Button", module).add(...)
// categorised under "Form"
storiesOf("Form|Selectbox", module).add(...)
Passing a component as props
This became an issue when I wanted to declare a custom <Route>
component while using React Router. I needed a way to pass a component to the custom <Route>
and then be able to render the component. This was surprisingly annoying. Tip, if you're able to view the type definitions for other modules, DO IT! I have found quite a few solutions from existing codebases, including this one;
import { ComponentType } from "react"
import { RouteProps } from "react-router-dom"
interface ICustomRoute extends RouteProps {
// Allows you to pass in components and then render them
component: ComponentType<any>
}
const CustomRoute = ({
component: Component,
...rest
}: ICustomRoute) => (
<Route
{...rest}
render={props => (
<Component {...props} />
)}
/>
)
Allow native HTML attributes as props
Imagine you want to create an <Input />
component, which should accept all properties of a <input />
element as well as an additional theme
object. To stop you from creating a custom definition for the component, it would be alot better to just extend the available props of an <input />
element, and, YOU CAN!
import { HTMLAttributes } from "react"
type Theme = "light" | "dark"
interface IProps extends HTMLAttributes<HTMLInputElement> {
// additional props if need
theme: {
variation: Theme
}
}
// You might want to extract certain props and your custom props
// instead of just spreading all the props
// if you don't have additional props swap IProps for HTMLAttributes<HTMLInputElement>
const Input ({ theme, ...props }: IProps) => (
<input
{...props}
className={`input input--${theme.variation}`}
/>
)
// Usage
<Input
onChange={(e) => handle(e)}
value={this.state.name}
name="name"
id="id"
theme={{
variation: "light"
}}
/>
Get device orientation
This is not really Typescript or React related, however, it could lead to something interesting. I can definitely imagine this being useful for a very cool but also very useless feature. Read more about it on MDN.
// Check if it is supported
if (window.DeviceOrientationEvent) {
window.addEventListener("deviceorientation", function(e) {
console.log({
x: e.alpha,
y: e.beta,
z: e.gamma
})
}, false)
}
Wrapping up
Each week we learn new techniques and different ways of thinking. I’d recommend to anyone to note down the different techniques you’ve learnt. Not only will you create a small knowledge base, you will also become more motivated when you see the progress you have made.
Thank you for reading my article, it really means a lot! ❤️ Please provide any feedback or comments, I'm always looking to improve and having meaningful discussions. This article was written as part of my #myweekinjs challenge, I have a few interesting articles there if you are interested in learning that.
Top comments (6)
Some tips:
Regarding avoiding extends and just using union types: this is the kind of statement that definitely should be explained/quantified. Very easy for a beginner to see a comment like this and take it the wrong way.
The Record type is really cool! Definitely going to add that into the article. I'll have a look into the union types a bit, looks promising!
Some more tips:
Avoid using
any
at all costs! You will benefit from specifying types more precisely, and if you happen not to know the exact type, you can useunknown
type – which is similar toany
, but it doesn't allow code to escape the TypeScript type system. More on that: stackoverflow.com/questions/514398...Never-ever use
Function
andobject
types. It's the same thing as withany
: you lose all type safety this way. For your example it's better to useReact.EventHandler<T>
type and its specializations likeMouseEventHandler
,PointerEventHandler
and so on, or specify function signatures explicitly:If you want to dive deeper in type-safe TypeScript development, please check out my article (it's currently on Medium, but if an interest arise, I'll re-post it here): levelup.gitconnected.com/typesafe-...
Prefer using HTMLProps over HTMLAttributes:
stackoverflow.com/questions/481981...
Thanks for the advice!