Passing Different Types of Props into React Functional Component — Typescript Generics
Problem
I have a component <Filters filters={filters} setFilters={setFilters} />
that I will use in different components and filters
passing in might be ContactFilter[]
or CompanyFilter[]
and corresponding setFilters
will be either (filters: ContactFilter[]) => void
or (filters: CompanyFilter[]) => void
. So in my <Filters />
component props, I allow filters
and setFilters
to be different types:
interface IFiltersProps {
filters: ContactFilter[] | CompanyFilter[];
setFilters: (filters: ContactFilter[] | CompanyFilter[]) => void;
}
export const Filters:React.FC<IFiltersProps> = ({
filters,
setFilters,
}) => {
...
}
The problem of this is, filters
and setFilters
types are independent from each other, meaning I could have filters
as ContactFilter[]
while having setFilters
as (filters: CompanyFilter[]) => void
, which then become a problem for my update filters function inside the component:
const handleCreateFilter = (
filterToCreate: ContactFilter | CompanyFilter
) => {
setFilters([...filters, filterToCreate]);
};
I got a complaint from typescript because filters
and filterToCreate
might be different types and they cannot make an array, or if they do make an array of the same type, setFilters
might require a different type of array…
But in my case, when I use <Filters />
as contact filters, the filters
passed in will be ContactFilter[]
and setFilters
will be (filters: ContactFilter[])=> void
, and filterToCreate
inside the component will also be ContactFilter
type. Vice versa, if I use <Filters />
as company filters, all the params will be CompanyFilter
.
The question is, how do I let typescript know that?
Solution — Generics
So the solution is to make parameters in <Filters /> generics:
interface IFiltersProps<T> {
filters: T[];
setFilters: (filters: T[]) => void;
}
export const Filters = <T extends ContactFilter | CompanyFilter>({
filters,
setFilters,
}) => {
...
}
By doing this, we are saying the filters
/setFilters
passed in can be either ContactFilter
or CompanyFilter
as long as they match each other.
And inside <Filter />
component, we can also reference the generic type like this and typescript won’t complain about the types:
const handleCreateFilter = (filterToCreate: T) => {
setFilters([...filters, filterToCreate]);
};
What generics can’t do
Another thing I was trying to do is to render different things depending on what type of parameters are passed into <Filter />
component, something like this:
<div>
{typeof filters === CompanyFilter[] ?
<div>this is company filters</div> :
<div>this is contact filters</div>
}
</div>
But my colleague pointed out typescript gets compiled into javascript meaning there is no types in the runtime so there is no way to know what type of parameters were passed in. So a workaround is to pass what type of filter it is to <Filter />
component:
interface IFiltersProps<T> {
filters: T[];
setFilters: (filters: T[]) => void;
type: "company"|"contact";
}
export const Filters = <T extends ContactFilter | CompanyFilter> ({
filters,
setFilters,
type,
}) => {
...
return (
<div>
{type === "company" ?
<div>this is company filters</div> :
<div>this is contact filters</div>
}
</div>
);
}
Hope this answers your question =)