DEV Community

Cover image for Discoveries I made while using Typescript and React
adro.codes
adro.codes

Posted on • Updated on • Originally published at myweekinjs.com

Discoveries I made while using Typescript and React

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
};
Enter fullscreen mode Exit fullscreen mode

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(...)
Enter fullscreen mode Exit fullscreen mode

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} />
    )}
  />
)
Enter fullscreen mode Exit fullscreen mode

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"
  }}
/>
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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.

👋 until next time!

Top comments (6)

Collapse
 
theodesp profile image
Theofanis Despoudis

Some tips:

  • It's better if you avoid extends and just use union types:
interface IProps  {
  theme: Theme;
}

type CustomInputProps = HTMLAttributes<HTMLInputElement> & IProps;
  • Only allow specific keys on an object: You are actually describing the Record type
type TListenerTypes = "onload" | "progress" | "error"

const x: Record<TListenerTypes, Function> = {
    a: "something", // wrong type
    progress: () => {},
    d: 10 // wrong type
};

Collapse
 
g1itcher profile image
G1itcher

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.

Collapse
 
hurricaneinteractive profile image
adro.codes

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!

Collapse
 
ybogomolov profile image
Yuriy Bogomolov • Edited

Some more tips:

  1. 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 use unknown type – which is similar to any, but it doesn't allow code to escape the TypeScript type system. More on that: stackoverflow.com/questions/514398...

  2. Never-ever use Function and object types. It's the same thing as with any: you lose all type safety this way. For your example it's better to use React.EventHandler<T> type and its specializations like MouseEventHandler, PointerEventHandler and so on, or specify function signatures explicitly:

type TListenerTypes = 'onload' | 'progress' | 'error';
type TListeners = Record<TListenerTypes, React.MouseEventHandler>;

// or:

type TListeners = Record<TListenerTypes, ((e: Event) => void)>;

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-...

Collapse
 
arvigeus profile image
Nikolay Stoynov

Prefer using HTMLProps over HTMLAttributes:
stackoverflow.com/questions/481981...

Collapse
 
hurricaneinteractive profile image
adro.codes

Thanks for the advice!