Avatar

Hallo, I'm Julia.

Some lesser known TypeScript notation

#formik #react #typescript

4 min read

Exclamation Mark / Bang operator

I recently came across the following notation in the codebase I was working on, and got very confused about what the exclamation mark / bang operator was for, after userInfo.

const initialFormValues: LocationData = {
  currentCountry: props.route.params?.userData?.userInfo!.current_country_code ? 'x' : 'y',
};

I thought it was new Javascript notation at first, but subsequently found out that it's specifically for Typescript. Basically, in this case, props.route.params? may or may not be present, hence the presence of the ?. It follows on that userData may therefore also not be present. However, if userData is present, userInfo will definitely be available, so it would not be correct to have userData?.userInfo?. If you just leave it as props.route.params?.userData?.userInfo.current_country_code however, the Typescript compiler will complain about userInfo potentially being undefined. So what to do?

Enter the bang operator. By adding ! after userInfo, you're telling Typescript to stop complaining because you're explicitly saying that it cannot be null or undefined here. Thanks for trying to guard us Typescript, but we know better in this case. 😉

Casting as "unknown"

The second Typescript notation that I recently learnt about was resetting types by casting as "unknown". What does this mean? Here's a concrete example:

Let's assume I have a Formik form. If you've not heard of Formik before, it's a very popular form library that's widely used in React and React Native apps (check out my other blog posts on Formik if you're interested). The nice thing about Formik is that it comes nicely typed and plays very nicely with a Typescript app. The other nice thing is that Formik works by effectively managing your form's state for you in such a way that you can easily abstract out your various form components and use Formik's context to access things like form values across different components.

So in the code example below, I have a Formik wrapper component, which has 3 different input fields and a submit button. I've abstracted out the 3 input fields into their own components, for MechanismQuestion, DateQuestion and LocationQuestion.

As I'll be running validation checks within each field component, I need to access the Formik state to see what the form values are, and therefore need to pass formikProps as a prop into each component (essentially passing down the current Formik form values that are in state into each component).

export interface MechanismData {
  mechanism: string;
}

export interface DateData {
  date: Date;
}

export interface LocationData {
  location: string;
}

<Formik
  // ... add Formik config here
>
  {(props) => { // props is provided by Formik
    return (
      <Form>
        <MechanismQuestion
          formikProps={props as unknown as FormikProps<MechanismData>}
        />
        <DateQuestion formikProps={props as unknown as FormikProps<DateData>} />
        <LocationQuestion
          formikProps={props as unknown as FormikProps<LocationData>}
        />
        <Button>Submit</Button>
      </Form>
    )
  }}
</Formik>

The formikProps contains all of the form's current values i.e. the value of mechanism, date and location. However, if say we just considered LocationQuestion, this component really only cares about location and I therefore want to strictly type this component to only accept the form values that pertain to location.

Simple right? Let's just recast props to a new type!

Unfortunately not so simple - if I had done something like the below, Typescript would have complained because props already has its defined type from Formik (containing everything in Formik's state) and therefore thinks we've made a mistake in wanting to take on another custom type of LocationData.

<LocationQuestion
  formikProps={props as FormikProps<LocationData>}
/>

What we therefore need to do in this case, is to recast props as unknown, before then recasting again into the specific type we want. What this does is to essentially erase the currently set type for props, so that we can happily define our new type for it.

<LocationQuestion
  formikProps={props as unknown as FormikProps<LocationData>}
/>

© 2016-2024 Julia Tan · Powered by Next JS.