Dealing with Strictness

Compiling the file, we get a few errors.

  • The venue-display file that is kicking off the call to React is not handling a potential undefined value.

  • The SortController is doing a parseInt with a potentially null value.

  • The debounce method in SearchController is unhappy and we may just need to restructure that.

  • The CalendarController has a few cases where we use target.dataset.

  • The thunk dispatches in App don’t like that they may potentially have nulls.

Using other strict options would give us more errors, in particular, I think the “no explicit any” might be a big problem, but I think this should be enough for us to deal with to get the idea.

Our venue_display file has basically the same issue four times over: we have code like element.dataset["rows"], which is now technically of type string | undefined, meaning the union type of a string or undefined, and we are passing it through to arguments that expect strings.

What we need to do is allow for the possibility that the values are undefined by creating default values. This is a little slapdash, but it works fine for our purposes:

 import​ * ​as​ React ​from​ ​"react"
 import​ * ​as​ ReactDOM ​from​ ​"react-dom"
 import​ App ​from​ ​"components/app"
 
 document.addEventListener(​"turbo:load"​, () => {
 const​ element = document.getElementById(​"react-element"​)
 if​ (element) {
  ReactDOM.render(
  <App
  rowCount=​{​parseInt(element.dataset.rowCount || ​"0"​, 10)​}
  seatsPerRow=​{​parseInt(element.dataset.seatsPerRow || ​"0"​, 10)​}
  concertId=​{​parseInt(element.dataset.concertId || ​"0"​, 10)​}
  />,
  element
  )
  }
 })

In all these cases, we’re using the or operator (||) and the fact that undefined values are false to specify a value to be passed on if the lookup is undefined.

The SortController has a similar issue, also solvable with the same solution in the sortValue method. (This file as written has a couple of minor warnings that you could also fix.)

The debounce function has a few problems. One is that the first line of the code sets timeoutId = null, which, on the one hand, means TypeScript now uses inference to assume the type will always be null, and on the other hand, the later call to clearTimeout wants the default value to be undefined. Also, we want to specify the window.setTimeout function, which explicitly returns a number for the timeoutId rather than the one from NodeJS that TypeScript is going to choose by default.

We also never specified a type for the functional argument or the return value, which should have the same type, namely a function that takes arbitrary arguments and returns void.

Put it all together, and you get this:

 basicSubmit(): ​void​ {
 if​ (​this​.inputTarget.value === ​""​) {
 this​.reset()
  } ​else​ {
 this​.formTarget.requestSubmit()
  }
 }
 
 submit(): ​void​ {
 this​.debounce(​this​.basicSubmit.bind(​this​))()
 }
 
 debounce(functionToDebounce, wait = 300) {
 let​ timeoutId = ​null
 
 return​ (...args) => {
  clearTimeout(timeoutId)
  timeoutId = setTimeout(() => {
  timeoutId = ​null
  functionToDebounce(...args)
  }, wait)
  }
 }

It turns out to be a little easier to manage this typing if debounce is a function rather than a method, which is fine. The timeoutId is now explicitly declared as a union type of a number and undefined, and it’s set to undefined both of the places it’s set.

I’ve declared a type called Debounceable, which is just a function that takes arguments and returns void. And then the actual debounce function declares a generic function extending Debounceable and says that both the argument function and the return function have that type, which specifies that the return value of debounce has the same signature as the argument function, which is what we want.

The issues in the CalendarController seem legit to me; it’s a failure of the code to deal with the case where the target.dataset.scheduleAttribute doesn’t exist. So either we need to explicitly say it could be undefined, or we need to provide a default value. I think the thing to do is allow the argument to be undefined, but then guard against it:

 scheduleElementFor(target: HTMLElement): HTMLElement | ​null​ {
 const​ scheduleId = target.dataset.scheduleId
 if​ (!scheduleId) {
 return​ ​null
  }
 return​ document.getElementById(scheduleId)
 }
 
 filter(): ​void​ {
 const​ everyDayUnselected = ​this​.everyDayUnselected()
 this​.calendarDayTargets.forEach((target: HTMLElement) => {
 const​ show =
  everyDayUnselected || target.dataset.cssStatusValue === ​"true"
 this​.toggleAssociatedConcerts(target.dataset.scheduleAttribute, !show)
  })
 }
 
 showAll(): ​void​ {
 this​.calendarDayTargets.forEach((target: HTMLElement) => {
  target.dataset.cssStatusValue = ​"false"
 this​.toggleAssociatedConcerts(target.dataset.scheduleAttribute, ​false​)
  })
 }
 
 toggleAssociatedConcerts(
  attributeName: string | ​undefined​,
  toggleValue: ​boolean
 ): ​void​ {
 if​ (!attributeName) {
 return
  }
  document
  .querySelectorAll(​`.concert[​${attributeName}​]`​)
  .forEach((element) => {
  element.classList.toggle(​"hidden"​, toggleValue)
  })
 }

We’ve changed the argument of toggleAssociatedConcerts to explicitly allow an undefined value, then we return out if the value is undefined, which might be technically unnecessary but does make it explicit that we’re dealing with the undefined value.

Finally, the issue in App.tsx is related to the type of the VenueThunk and is fixed by changing the null argument in that type definition to undefined.

And now our code compiles again, just a little bit stricter. We’ve fixed one potential bug in the CalendarController and, in the other cases, mostly just made the code more consistent.

These potential errors are minor, but they allow us to change the way we have been thinking about type checking. So far, we’ve been looking at type checking as a form of communication between the coders and the compiler, telling the compiler about the types so that the compiler can warn you about mismatches between different parts of the code.

Now we can start looking at type checking as a way to prevent run-time errors by making it impossible for invalid states to make it past the compiler. In this case, the invalid state is the case where the selector that we are assuming is in the DOM isn’t there. In other words, the compiler here is alerting us to a case that our code doesn’t handle, and we need to figure out what we want to do.

Our response to this compiler error depends on how confident we are that the null condition won’t happen. If we’re pretty certain we know more about the code than the compiler does, we can make the compiler error go away by using a type assertion.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.82.79