Search
We will create a <Header>
component for your application with Ant Design's <AutoComplete>
component.
We will now examine how to search within the application with this component.
To do this, let's first create our <Header>
component.
import { AntdLayout, AutoComplete, Input, Icons } from "@pankod/refine-antd";
const { SearchOutlined } = Icons;
export const Header: React.FC = () => {
return (
<AntdLayout.Header
style={{
padding: "0px 24px",
backgroundColor: "#FFF",
}}
>
<AutoComplete
style={{ width: "100%", maxWidth: "550px" }}
filterOption={false}
>
<Input
size="large"
placeholder="Search posts or categories"
suffix={<SearchOutlined />}
/>
</AutoComplete>
</AntdLayout.Header>
);
};
We created the <Header>
component as we want it to appear. We have not done anything for any search process at this stage. We just created the UI.
Let's not forget to pass the <Header>
component to the <Refine>
component in App.tsx
as below.
import { Refine } from "@pankod/refine-core";
import {
Layout,
ReadyPage,
notificationProvider,
ErrorComponent,
} from "@pankod/refine-antd";
import routerProvider from "@pankod/refine-react-router-v6";
import dataProvider from "@pankod/refine-simple-rest";
import "@pankod/refine-antd/dist/reset.css";
import { Header } from "components";
const API_URL = "https://api.fake-rest.refine.dev";
const App: React.FC = () => {
return (
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider(API_URL)}
Layout={Layout}
ReadyPage={ReadyPage}
notificationProvider={notificationProvider}
catchAll={<ErrorComponent />}
Header={Header}
/>
);
};
export default App;
Now let's get our <AutoComplete>
input ready to search. So, let's fetch our posts according to the value entered in our input.
To fetch more than one record, we will use the useList
data hook, and we will filter and fetch this data according to the search value.
Before we start, let's create the interfaces of our <AutoComplete>
's options
property and the post source.
export interface IPost {
id: number;
title: string;
}
export interface ICategory {
id: number;
title: string;
}
export interface IOptionGroup {
value: string;
label: string | React.ReactNode;
}
export interface IOptions {
label: string | React.ReactNode;
options: IOptionGroup[];
}
import { useState, useEffect } from "react";
import { useList } from "@pankod/refine-core";
import {
AntdLayout,
AutoComplete,
Input,
Icons,
Typography,
} from "@pankod/refine-antd";
import routerProvider from "@pankod/refine-react-router-v6";
const { Link } = routerProvider;
const { Text } = Typography;
const { SearchOutlined } = Icons;
import { IOptions, IPost } from "interfaces";
// To be able to customize the option title
const renderTitle = (title: string) => {
return (
<Text strong style={{ fontSize: "16px" }}>
{title}
</Text>
);
};
// To be able to customize the option item
const renderItem = (title: string, resource: string, id: number) => {
return {
value: title,
label: (
<Link to={`/${resource}/show/${id}`}>
<Text>{title}</Text>
</Link>
),
};
};
export const Header: React.FC = () => {
const [value, setValue] = useState<string>("");
const [options, setOptions] = useState<IOptions[]>([]);
const { refetch: refetchPosts } = useList<IPost>({
resource: "posts",
config: {
filters: [{ field: "title", operator: "contains", value }],
},
queryOptions: {
enabled: false,
onSuccess: (data) => {
const postOptionGroup = data.data.map((item) =>
renderItem(item.title, "posts", item.id),
);
if (postOptionGroup.length > 0) {
setOptions([
{
label: renderTitle("Posts"),
options: postOptionGroup,
},
]);
}
},
},
});
useEffect(() => {
setOptions([]);
refetchPosts();
}, [value]);
return (
<AntdLayout.Header
style={{
padding: "0px 24px",
backgroundColor: "#FFF",
}}
>
<AutoComplete
style={{ width: "100%", maxWidth: "550px" }}
filterOption={false}
options={options}
onSearch={(value: string) => setValue(value)}
>
<Input
size="large"
placeholder="Search posts or categories"
suffix={<SearchOutlined />}
/>
</AutoComplete>
</AntdLayout.Header>
);
};
We created states to dynamically manage the value
and options
properties of the <AutoComplete>
component. The useList
hook is triggered whenever the value changes. Likewise, the filter used to fetch the data is updated each time the value changes.
Search value is currently only searched and fetched inside posts. Let's update our code to search both posts and categories according to the search value.
...
export const Header: React.FC = () => {
const [value, setValue] = useState<string>("");
const [options, setOptions] = useState<IOptions[]>([]);
const { refetch: refetchPosts } = useList<IPost>({
resource: "posts",
config: {
filters: [{ field: "title", operator: "contains", value }],
},
queryOptions: {
enabled: false,
onSuccess: (data) => {
const postOptionGroup = data.data.map((item) =>
renderItem(item.title, "posts", item.id),
);
if (postOptionGroup.length > 0) {
setOptions((prevOptions) => [
...prevOptions
{
label: renderTitle("Posts"),
options: postOptionGroup,
},
]);
}
},
},
});
const { refetch: refetchCategories } = useList<ICategory>({
resource: "categories",
config: {
filters: [{ field: "q", operator: "contains", value }],
},
queryOptions: {
enabled: false,
onSuccess: (data) => {
const categoryOptionGroup = data.data.map((item) =>
renderItem(item.title, "categories", item.id),
);
if (categoryOptionGroup.length > 0) {
setOptions((prevOptions) => [
...prevOptions,
{
label: renderTitle("Categories"),
options: categoryOptionGroup,
},
]);
}
},
},
});
useEffect(() => {
setOptions([]);
refetchPosts();
refetchCategories();
}, [value]);
return (
<AntdLayout.Header
style={{
padding: "0px 24px",
backgroundColor: "#FFF",
}}
>
...
</AntdLayout.Header>
);
};
By doing the same implementation on your other resources, you can search for more than one resource with a value.
Example
npm create refine-app@latest -- --example search