documentation/versioned_docs/version-3.xx.xx/advanced-tutorials/data-provider/strapi-v4.md
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
refine supports the features that come with Strapi-v4.
A few of the Strapi-v4 API features are as follows:
metaData allows us to use the above features in hooks. Thus, we can fetch the data according to the parameters we want.
Hooks and components that support metaData:
:::note
There is no need to use metaData for sorting, pagination, and, filters. Sorting, pagination, and, filters will be handled automatically by the strapi-v4 dataProvider.
:::
:::info Normally, strapi-v4 backend returns data in the following format:
{
"id": 1,
"attributes": {
"title": "My title",
"content": "Long content...",
}
However, we can use normalizeData to customize the data returned by the backend. So, our data will look like:
{
"id": 1,
"title": "My title",
"content": "Long content..."
}
:::
:::caution
To make this example more visual, we used the @pankod/refine-antd package. If you are using Refine headless, you need to provide the components, hooks, or helpers imported from the @pankod/refine-antd package.
:::
import { Refine, AuthProvider } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-router-v6";
//highlight-next-line
import { DataProvider } from "@pankod/refine-strapi-v4";
import routerProvider from "@pankod/refine-react-router-v6";
const App: React.FC = () => {
return (
<Refine
authProvider={authProvider}
//highlight-next-line
dataProvider={DataProvider("API_URL")}
routerProvider={routerProvider}
Layout={Layout}
ReadyPage={ReadyPage}
notificationProvider={useNotificationProvider}
catchAll={<ErrorComponent />}
/>
);
};
Let's examine how API parameters that come with Strapi-v4 are used with metaData. Then, let's see how it is used in the application.
We created two collections on Strapi as posts and categories and added a relation between them. For detailed information on how to create a collection, you can check here.
<Tabs defaultValue="posts" values={[ {label: 'posts', value: 'posts'}, {label: 'categories', value: 'categories'} ]}> <TabItem value="posts">
posts has the following fields:
idtitlecontentcategorycreatedAtlocalecategories has the following fields:
idtitleTo select only some fields, we must specify these fields with `metaData``.
Refer to the Fields Selection documentation for detailed information. →
const { tableProps } = useTable<IPost>({
metaData: {
fields: ["id", "title"],
},
});
const { tableProps } = useTable<IPost>({
metaData: {
fields: "*",
},
});
When sending the request, we can specify which fields will come, so we send fields in metaData to hooks that we will fetch data from. In this way, you can perform the queries of only the fields you want.
import { useState } from "react";
import { IResourceComponentsProps } from "@pankod/core";
import {
List,
Table,
useTable,
getDefaultSortOrder,
FilterDropdown,
Select,
useSelect,
Space,
EditButton,
DeleteButton,
} from "@pankod/refine-antd";
import { IPost } from "interfaces";
import { API_URL } from "../../constants";
export const PostList: React.FC<IResourceComponentsProps> = () => {
const { tableProps, sorter } = useTable<IPost>({
metaData: {
// highlight-start
fields: ["id", "title"],
// highlight-end
},
});
return (
<List>
<Table
{...tableProps}
rowKey="id"
pagination={{
...tableProps.pagination,
showSizeChanger: true,
}}
>
<Table.Column
dataIndex="id"
title="ID"
defaultSortOrder={getDefaultSortOrder("id", sorter)}
sorter={{ multiple: 3 }}
/>
<Table.Column
dataIndex="title"
title="Title"
defaultSortOrder={getDefaultSortOrder("title", sorter)}
sorter={{ multiple: 2 }}
/>
<Table.Column<{ id: string }>
title="Actions"
render={(_, record) => (
<Space>
<EditButton hideText size="small" recordItemId={record.id} />
<DeleteButton hideText size="small" recordItemId={record.id} />
</Space>
)}
/>
</Table>
</List>
);
};
By default, relations are not populated when fetching entries.
The populate parameter is used to define which fields will be populated.
Refer to the Relations Population documentation for detailed information. →
const { tableProps } = useTable<IPost>({
metaData: {
populate: ["category", "cover"],
},
});
const { tableProps } = useTable<IPost>({
metaData: {
populate: "*",
},
});
It should be noted that Strapi-V4 allows populating relations more than 1 level.
const { tableProps } = useTable<IPost>({
metaData: {
populate: {
category: {
populate: ["cover"],
},
cover: {
populate: [""],
},
},
},
});
In order to pull the categories related to the posts, we can now show the categories in our list by defining the metaData populate parameter.
import { IResourceComponentsProps } from "@pankod/refine-core";
import {
List,
Table,
useTable,
getDefaultSortOrder,
FilterDropdown,
Select,
useSelect,
Space,
EditButton,
DeleteButton,
} from "@pankod/refine-antd";
import { IPost } from "interfaces";
import { API_URL } from "../../constants";
export const PostList: React.FC<IResourceComponentsProps> = () => {
const { tableProps, sorter } = useTable<IPost>({
metaData: {
fields: ["id", "title"],
// highlight-start
populate: ["category"],
// highlight-end
},
});
// highlight-start
const { selectProps } = useSelect({
resource: "categories",
optionLabel: "title",
optionValue: "id",
});
// highlight-end
return (
<List>
<Table
{...tableProps}
rowKey="id"
pagination={{
...tableProps.pagination,
showSizeChanger: true,
}}
>
<Table.Column
dataIndex="id"
title="ID"
defaultSortOrder={getDefaultSortOrder("id", sorter)}
sorter={{ multiple: 3 }}
/>
<Table.Column
dataIndex="title"
title="Title"
defaultSortOrder={getDefaultSortOrder("title", sorter)}
sorter={{ multiple: 2 }}
/>
//highlight-start
<Table.Column
dataIndex={["category", "title"]}
title="Category"
filterDropdown={(props) => (
<FilterDropdown {...props}>
<Select
style={{ minWidth: 200 }}
mode="multiple"
placeholder="Select Category"
{...selectProps}
/>
</FilterDropdown>
)}
/>
//highlight-end
<Table.Column<{ id: string }>
title="Actions"
render={(_, record) => (
<Space>
<EditButton hideText size="small" recordItemId={record.id} />
<DeleteButton hideText size="small" recordItemId={record.id} />
</Space>
)}
/>
</Table>
</List>
);
};
/me requestIf you need to the population for the /me request you can use it like this in your authProvider.
const strapiAuthHelper = AuthHelper(API_URL + "/api");
strapiAuthHelper.me("token", {
metaData: {
populate: ["role"],
},
});
:::note The Draft & Publish feature should be enabled on Strapi. :::
Refer to the Publication State documentation for detailed information. →
live: returns only published entries
preview: returns draft and published entries
const { tableProps } = useTable<IPost>({
metaData: {
publicationState: "preview",
},
});
We can list the posts separately according to the published or draft information.
// highlight-next-line
import { useState } from "react";
import { IResourceComponentsProps } from "@pankod/refine-core";
import {
List,
Table,
useTable,
getDefaultSortOrder,
FilterDropdown,
Select,
useSelect,
DateField,
Space,
EditButton,
DeleteButton,
// highlight-start
Form,
Radio,
Tag,
// highlight-end
} from "@pankod/refine-antd";
import { IPost } from "interfaces";
import { API_URL } from "../../constants";
export const PostList: React.FC<IResourceComponentsProps> = () => {
// highlight-start
const [publicationState, setPublicationState] = useState("live");
// highlight-end
const { tableProps, sorter } = useTable<IPost>({
metaData: {
fields: ["id", "title"],
populate: ["category"],
// highlight-start
publicationState,
// highlight-end
},
});
const { selectProps } = useSelect({
resource: "categories",
optionLabel: "title",
optionValue: "id",
});
return (
<List>
//highlight-start
<Form
layout="inline"
initialValues={{
publicationState,
}}
>
<Form.Item label="Publication State" name="publicationState">
<Radio.Group onChange={(e) => setPublicationState(e.target.value)}>
<Radio.Button value="live">Published</Radio.Button>
<Radio.Button value="preview">Draft and Published</Radio.Button>
</Radio.Group>
</Form.Item>
</Form>
//highlight-end
<Table
{...tableProps}
rowKey="id"
pagination={{
...tableProps.pagination,
showSizeChanger: true,
}}
>
<Table.Column
dataIndex="id"
title="ID"
defaultSortOrder={getDefaultSortOrder("id", sorter)}
sorter={{ multiple: 3 }}
/>
<Table.Column
dataIndex="title"
title="Title"
defaultSortOrder={getDefaultSortOrder("title", sorter)}
sorter={{ multiple: 2 }}
/>
<Table.Column
dataIndex={["category", "title"]}
title="Category"
filterDropdown={(props) => (
<FilterDropdown {...props}>
<Select
style={{ minWidth: 200 }}
mode="multiple"
placeholder="Select Category"
{...selectProps}
/>
</FilterDropdown>
)}
/>
//highlight-start
<Table.Column
dataIndex="publishedAt"
title="Status"
render={(value) => {
return (
<Tag color={value ? "green" : "blue"}>
{value ? "Published" : "Draft"}
</Tag>
);
}}
/>
//highlight-end
<Table.Column<{ id: string }>
title="Actions"
render={(_, record) => (
<Space>
<EditButton hideText size="small" recordItemId={record.id} />
<DeleteButton hideText size="small" recordItemId={record.id} />
</Space>
)}
/>
</Table>
</List>
);
};
:::tip To fetch content for a locale, make sure it has been already added to Strapi in the admin panel :::
Refer to the Locale documentation for detailed information. →
const { tableProps } = useTable<IPost>({
metaData: {
locale: "de",
},
});
With the local parameter feature, we can fetch posts and categories created according to different languages.
import { useState } from "react";
import { IResourceComponentsProps } from "@pankod/refine-core";
import {
List,
Table,
useTable,
getDefaultSortOrder,
FilterDropdown,
Select,
useSelect,
Space,
EditButton,
DeleteButton,
Form,
Radio,
Tag,
} from "@pankod/refine-antd";
import { IPost } from "interfaces";
import { API_URL } from "../../constants";
export const PostList: React.FC<IResourceComponentsProps> = () => {
//highlight-start
const [locale, setLocale] = useState("en");
//highlight-end
const [publicationState, setPublicationState] = useState("live");
const { tableProps, sorter } = useTable<IPost>({
metaData: {
populate: ["category", "cover"],
//highlight-start
locale,
//highlight-end
publicationState,
},
});
const { selectProps } = useSelect({
resource: "categories",
optionLabel: "title",
optionValue: "id",
//highlight-start
metaData: { locale },
//highlight-end
});
return (
<List>
<Form
layout="inline"
//highlight-start
initialValues={{
locale,
publicationState,
}}
//highlight-end
>
//highlight-start
<Form.Item label="Locale" name="locale">
<Radio.Group onChange={(e) => setLocale(e.target.value)}>
<Radio.Button value="en">English</Radio.Button>
<Radio.Button value="de">Deutsch</Radio.Button>
</Radio.Group>
</Form.Item>
//highlight-end
<Form.Item label="Publication State" name="publicationState">
<Radio.Group onChange={(e) => setPublicationState(e.target.value)}>
<Radio.Button value="live">Published</Radio.Button>
<Radio.Button value="preview">Draft and Published</Radio.Button>
</Radio.Group>
</Form.Item>
</Form>
<Table
{...tableProps}
rowKey="id"
pagination={{
...tableProps.pagination,
showSizeChanger: true,
}}
>
<Table.Column
dataIndex="id"
title="ID"
defaultSortOrder={getDefaultSortOrder("id", sorter)}
sorter={{ multiple: 3 }}
/>
<Table.Column
dataIndex="title"
title="Title"
defaultSortOrder={getDefaultSortOrder("title", sorter)}
sorter={{ multiple: 2 }}
/>
<Table.Column
dataIndex={["category", "title"]}
title="Category"
filterDropdown={(props) => (
<FilterDropdown {...props}>
<Select
style={{ minWidth: 200 }}
mode="multiple"
placeholder="Select Category"
{...selectProps}
/>
</FilterDropdown>
)}
/>
<Table.Column
dataIndex="publishedAt"
title="Status"
render={(value) => {
return (
<Tag color={value ? "green" : "blue"}>
{value ? "Published" : "Draft"}
</Tag>
);
}}
/>
<Table.Column<{ id: string }>
title="Actions"
render={(_, record) => (
<Space>
<EditButton hideText size="small" recordItemId={record.id} />
<DeleteButton hideText size="small" recordItemId={record.id} />
</Space>
)}
/>
</Table>
</List>
);
};
metaData UsagesWhen creating and editing posts you can use these API parameters in metaData:
const { formProps, saveButtonProps, queryResult } = useForm<IPost>({
metaData: { publicationState: "preview" },
});
const { formProps, saveButtonProps, queryResult } = useForm<IPost>({
metaData: { populate: ["category", "cover"] },
});
const { selectProps } = useSelect({
metaData: { locale: "en" },
});
:::note Demo Credentials Username: [email protected]
Password: demodemo :::
<CodeSandboxExample path="data-provider-strapi-v4" />