Previously I wrote about RTK, this story will cover other details of its use.
Imagine we need to display the same kind of data but from different resources depending on the incoming parameter value. In this case, we will have the component with two hooks that handle requests and receive data.
Basically, the representation of API looks like this:
export const configApi = baseApi.injectEndpoints({
endpoints: build => ({
fetchConfigDetails: build.query<IConfig[], IBaseRequest>({
query: arg => `/config-details`,
transformResponse: (x: IResponse<IConfig[]>) => x.result,
providesTags: ['ConfigDetails'],
}),
fetchConfigRunDetails: build.query<IConfig[], IRunRequest>({
query: arg => `/${arg.id}/config-details`,
transformResponse: (x: IResponse<IConfig[]>) => x.result,
providesTags: ['ConfigRunDetails'],
}),
}),
overrideExisting: true,
});
It has two endpoints and the main difference between them is the argument ‘id’, so actually the resources are different. So the cache will be also different, one for ConfigDetails and one for ConfigRunDetails.
For the clarification on where hooks take their names, I will provide the next example:
export const {
useFetchConfigDetailsQuery,
useFetchConfigRunDetailsQuery,
} = configApi;
The hook name consists of the prefix ‘use’, the endpoint name fetchConfigDetails, and the postfix of the request’s type (a function that is used to create the endpoint) ‘build.query’ → useFetchConfigDetailsQuery.
And these two hooks will be used in one component:
const RowResultsDetails: React.FC<IProps> = () => {
const { id } = useParams<IRouteParams>();
const fetchDetails = useFetchConfigDetailsQuery(
{ },
{
skip: !!id,
selectFromResult: ({ data, isFetching }) => ({
isFetching: isFetching,
data: selectConfigDetails(data),
}),
}
);
const fetchRunDetails = useFetchConfigRunDetailsQuery(
{ id },
{
skip: !id,
selectFromResult: ({ data, isFetching }) => ({
isFetching: isFetching,
data: selectRunConfigDetails(data),
}),
}
);
const { data, isFetching } = id ? fetchRunDetails : fetchDetails;
return (
<div>
<Section.Title>({data.length})</Section.Title>
...
</div>
);
}
As you may know, hooks in React must have the same order and can’t be inside some ‘if else’ condition. In RTK because of such constraint, there is a special property ‘skip’ direct in the hook second parameter (options). With this option, you can tell RTK to execute or not execute the hook. Additionally, for render optimization, RTK provides the possibility to use reselect library to choose the data that you need in this particular component. In other words, selectConfigDetails and selectRunConfigDetails are selectors to memoize data from the cache. If the request will be invoked and selected data is not changed, nothing will happen in the component.
The selector actually has no difference from the store selector except you don’t provide the state to it.
const emptyArray = [];
export const selectConfigDetails = createSelector(
[
(data?: IConfig[]) => data
],
(data) => data?.filter(c => c.isAvailable) ?? emptyArray
);
An important point here is if you are using the same selector for both hooks it will cause redundant rerender. In other words ‘selector’ is unique and remembers the result. For the same selector, there will be two different values in the result. Be careful to not rerender the component frequently. As well if you prefer to use the useMemo hook to memoize data, in this scenario there needs to be two different useMemo usages.