DEV Community

Bionic Julia
Bionic Julia

Posted on • Originally published at bionicjulia.com on

Some lesser known TypeScript notation

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

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

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

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

Top comments (7)

Collapse
 
click2install profile image
click2install

The non-null assertion operator is almost always a bad idea, it doesn't hold under refactor. In your example it would be better to combine nullish coalescing with the optional chaining so you provide a default value ... throw expressions will be a good fit here once included in the spec too. In general, optional chaining is best used with nullish coalescing for code safety, especially under refactor. Nice article to bring awareness though 👍.

Collapse
 
bionicjulia profile image
Bionic Julia

Thanks so much for the feedback! :) I'll look into this further.

Collapse
 
haaxor1689 profile image
Maroš Beťko • Edited

Nice overview of these 2 features of TS, but you should emphasise that they are both manually turning off/overriding set up rules which is an easy way to shoot yourself in foot if you aren't absolute sure about what you are doing. If you decide to use bang operator or cast through unknown you are giving up type safety on that part of code which makes it more prone to bugs (for example during refactoring).

Collapse
 
bionicjulia profile image
Bionic Julia

Thanks for taking the time to highlight this Maroš!

Collapse
 
mycarrysun profile image
Mike Harrison

An operator I just learned about yesterday is the is operator. Useful for determining types when static analysis just doesn't cut it.

Collapse
 
bionicjulia profile image
Bionic Julia

Ah nice one Mike - thanks for sharing!

Collapse
 
ricardo_borges profile image
Ricardo Borges

Nice. I also found out about the bang operator recently ;)