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>}
/>
Top comments (7)
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 👍.
Thanks so much for the feedback! :) I'll look into this further.
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).
Thanks for taking the time to highlight this Maroš!
An operator I just learned about yesterday is the
is
operator. Useful for determining types when static analysis just doesn't cut it.Ah nice one Mike - thanks for sharing!
Nice. I also found out about the bang operator recently ;)