Passing Different Types of Props into React Functional Component — Typescript Generics

May Chen
NEXL Engineering
Published in
3 min readFeb 2, 2021

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 =)

--

--

May Chen
NEXL Engineering

A developer who occasionally has existential crisis and thinks if we are heading to the wrong direction, technology is just getting us there sooner.