I use RTK Query to consume an /items/filter
API. In the codes below, the debouncedQuery holds a value of query string parameter to be used to call a filter API endpoint. e.g.: In the /items/filter?item_name=pencil
and return the matched results. When it's empty, then /items/filter
is called and returns a limited number of results (20 items).
So far, /items/filter
returns the results and are displayed as expected while the application is started.
When I passed a filter param /items/filter?item_name={debouncedQuery}
, it returned the results. But, it was not shown because, in the Item Detail Component, the selectItemById
does not return any result with the provided ids.
Bellow are sample code:
Search Item Component:
export function SearchItem(props: SearchItemProps) {
const {onSelectedItem} = props;
const [itemName, setItemName] = useState<string|undefined>(undefined);
const debouncedQuery = useDebounce(itemName, 500);
const {currentData: items, refetch, isLoading, isFetching, isSuccess} = useFilterItemsQuery(debouncedQuery, {
refetchOnFocus: true,
refetchOnMountOrArgChange: true,
skip: false,
selectFromResult: ({data, error, isLoading, isFetching, isSuccess}) => ({
currentData: data,
error,
isLoading,
isFetching,
isSuccess
}),
});
const ids = items?.ids
useEffect(() => {
refetch();
}, []);
const handleOnChange = (event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value.toLowerCase();
setItemName(value);
}
let content;
if (isLoading || isFetching) {
content = <div style={{display: 'flex', justifyContent: 'center', alignItems: 'center', marginTop: 50}}>
<Spinner animation="grow" variant="dark"/>
</div>;
}
if (!ids?.length) {
content = <Alert variant="dark">
<Alert.Heading>Oh snap! What happened?</Alert.Heading>
<p>The item: {itemName} is not found!</p>
</Alert>;
}
if (isSuccess) {
content = ids?.length ? <ListGroup>
{ids.map((itemId: EntityId, index: number) => {
return <ItemDetail key={index} index={index} id={itemId} onSelectedItem={onSelectedItem}/>
})}
</ListGroup> : null;
}
return (
<>
<Card className="bg-secondary bg-opacity-10 pt-3">
<Card.Header>
<SearchForm name="item_name" placeholder="Search Item" onChange={handleOnChange}/>
</Card.Header>
<Card.Body style={{minHeight: 544, maxHeight: 544, overflowY: "auto"}}>
{content}
</Card.Body>
</Card>
</>
)
}
Item Detail Component
export function ItemDetail(props: ItemProps) {
const {index, id, onSelectedItem} = props;
const item = useAppSelector(state => {
return selectItemById(state, id);
});
console.log("item: ", item);
const handleOnClickedItem = (selectedItem: Item) => {
onSelectedItem(selectedItem);
}
return <ListGroup.Item
action
onClick={() => handleOnClickedItem(item!)}
className={"d-flex justify-content-between align-items-start"}
key={item?.item_uuid}
variant={index % 2 === 0 ? "light" : "dark"}
>
<div className="ms-2 me-auto">
<div>{item?.item_name}</div>
</div>
<Badge bg="dark" className={"bg-opacity-50"} style={{minWidth: 100}}>
<NumberFormat
value={item?.price}
displayType={'text'}
thousandSeparator={true}
prefix={''}
renderText={(formattedValue: string) => <div>{formattedValue}</div>}
/>
</Badge>
</ListGroup.Item>
}
Item ApiSlice
const itemsAdapter = createEntityAdapter<Item>()
const initialState = itemsAdapter.getInitialState();
export const itemApiSlice = catalogApiSlice.injectEndpoints({
endpoints: builder => ({
filterItems: builder.query({
query: (arg) => {
const url = CATALOG_FILTER_ITEMS
if (arg) {
return {
url,
params: {item_name: arg},
};
} else {
return {url};
}
},
transformResponse(response: { data: Item[] }) {
return itemsAdapter.setAll(initialState, response.data)
},
providesTags: (result: Item[] | any) => {
if (result.ids.length) {
// @ts-ignore
return [...result.ids.map(({id}) => ({type: 'Items' as const, id})), {
type: 'Items',
id: 'FILTER_LIST'
}];
} else return [{type: 'Items', id: 'FILTER_LIST'}];
},
}),
getItems: builder.query({
query: () => CATALOG_ITEMS,
transformResponse(response: { data: Item[] }) {
return response.data;
},
providesTags: (result, error, arg) => {
// @ts-ignore
return result
? [
...result.map(({id}) => ({type: 'Items' as const, id})),
{type: 'Items', id: 'LIST'},
]
: [{type: 'Items', id: 'LIST'}]
},
}),
getItem: builder.query({
query: id => {
return {
url: `${CATALOG_ITEMS}/${id}`,
};
},
transformResponse(response: { data: Item }) {
return response.data;
},
providesTags: (result, error, arg) => {
// @ts-ignore
return result
? [
{type: 'Items' as const, id: result.id},
{type: 'Items', id: 'DETAIL'},
]
: [{type: 'Items', id: 'DETAIL'}]
},
}),
})
})
export const {
useGetItemsQuery,
useFilterItemsQuery,
useGetItemQuery
} = itemApiSlice
export const selectItemsResult = itemApiSlice.endpoints.filterItems.select();
const selectItemsData = createDraftSafeSelector(
selectItemsResult,
itemsResult => {
return itemsResult.data
}
)
export const {
selectAll: selectAllItems,
selectById: selectItemById,
selectIds: selectItemIds
} = itemsAdapter.getSelectors((state: any) => selectItemsData(state) ?? initialState);
I am wondering how I can get that debouncedQuery
in select()
or how to update the memoized select in each /items/filter?item_name={debouncedQuery}
.
Thank you