Where TypeScript Will Fail You
TypeScript is great. I made the switch a few years ago when I was starting on a new project. I haven’t looked back since. I feel much more confident in the code that I produce, that it will work (mostly) as I expect it to. Sometimes, that confidence can come back to bite me.
Today, I was adding some functionality to a clearFilters
function we have. It looks something like this:
const [activeFilters, setActiveFilters] = useState<Filter[]>(initialFilters)const clearFilters = () => {
setActiveFilters([])
}
Pretty simple. We have a filtering system and this resets the filters. It’s used in our Filters
component and also as a shared function so we can clear filters outside of the Filters
component when we need to.
In Filters
, it looks something like this:
interface Props {
filters: Filter[]
clearFilters: () => void
}const Filters: React.FC<Props> = ({
return (
<div>
... the filters <button
onClick={clearFilters}
>
Clear All
</button>
</div>
)
})
Again, pretty simple. Clicking the Clear All
button clears all the filters, which triggers a new query to the API in a higher level component. Now, what if I want to make it more complicated? I want to be able to clear all the filters except a few that I want to remain.
Great! Let’s update our clearFilters
function
const clearFilters = (except?: FilterType[]) => {
if(except){
setActiveFilters(activeFilters.filter((f) => except.includes(f.type))
} else {
setActiveFilters([])
}
}
We’ve updated the library. Everything compiles. No TypeScript errors! Hurrah! Push it to Prod!
Except
I’m getting an exception every time I try to clear filters that says
except.includes
is not a function! What the what?!
I thought I really gotten one over on TypeScript with this error. Turns out, it’s a pretty simple mistake. But as with all mistakes, it’s only simple once you have figured it out.
My (Simple?) Mistake
Let’s look at Filters
again.
interface Props {
filters: Filter[]
clearFilters: () => void
}const Filters: React.FC<Props> = ({
return (
<div>
... the filters <button
onClick={clearFilters}
>
Clear All
</button>
</div>
)
})
See it? My Props are no longer valid. I need to update them to reflect the new clearFilters
function.
interface Props {
filters: Filter[]
clearFilters: (except?: FilterType[]) => void
}
Once I’ve done that, my onClick={clearFilters}
line is going to throw a TypeScript compile error. The types don’t match! Setting onClick to the function without any parameters means the mouse event from the click is going to get passed to the function.
To fix that, we make another small update:
<button onClick={() => clearFilters()}>All Clear</button>
Now we are actually all in the clear and everything is working as it should!
Lessons Learned
- When passing a function as a prop, update the type when you update the function
TypeScript is great, but it will only do what I tell it to do. If I had been importing the clearFilters
function directly there wouldn’t have been a problem here. Because I was passing it in as a parameter, I needed to make sure the prop type matched the function I was passing in.
2. Write automated tests
In this case, my error was caught by our Cypress tests on the build server. If not for that, it’s possible this code could have released into production without anyone realizing it was broken. These automated tests prevented major headaches, especially ahead of a deploy late on a Friday.
Hope my mistakes can save you from some in the future! Where have you run into things with TypeScript that happened differently than you expected? What lessons have you learned along the way?