Skip to main content

Listing Records

In this step, we'll be learning about the Refine's useList hook to fetch a list of records from our API. We'll also learn about pagination, sorting and filtering through the useList hook.

Implementing the getList Method

To list records using Refine's hooks, first we need to implement the getList method in our data provider. This method will be called when we use the useList hook or its extensions in our components.

The getList method accepts resource, pagination, sorters, filters and meta properties.

  • resource refers to the entity we're fetching.
  • pagination is an object containing the current and pageSize properties.
  • sorters is an array containing the sorters we're using.
  • filters is an array containing the filters we're using.
  • meta is an object containing any additional data passed to the hook.

Our fake API has products entity and expects us to list records using the /products endpoint. So, we'll be using the resource, pagination, sorters and filters properties to make our request.

To make the implementation process easier, we'll start by implementing the getList method without pagination, sorting, or filtering, and then gradually add these features to our implementation.

Update your src/providers/data-provider.ts file by adding the following lines:

src/providers/data-provider.ts
import type { DataProvider } from "@refinedev/core";

const API_URL = "https://api.fake-rest.refine.dev";

export const dataProvider: DataProvider = {
getList: async ({ resource, pagination, filters, sorters, meta }) => {
const response = await fetch(`${API_URL}/${resource}`);

if (response.status < 200 || response.status > 299) throw response;

const data = await response.json();

return {
data,
total: 0, // We'll cover this in the next steps.
};
},
/* ... */
};

Using the useList Hook

After implementing the getList method, we'll be able to call useList hook and fetch a list of records from our API. Let's create a component called ListProducts and mount it inside our <Refine /> component.

Then, we'll use the useList hook inside our ListProducts to fetch a list of records of products entity from our API.

Update your src/pages/products/list.tsx file by adding the following lines:

src/pages/products/list.tsx
import { useList } from "@refinedev/core";

export const ListProducts = () => {
const { data, isLoading } = useList({ resource: "products" });

if (isLoading) {
return <div>Loading...</div>;
}

return (
<div>
<h1>Products</h1>
<ul>
{data?.data?.map((product) => (
<li key={product.id}>
<p>
{product.name}
<br />
Price: {product.price}
<br />
Material: {product.material}
</p>
</li>
))}
</ul>
</div>
);
};

Finally, we'll mount our ListProducts component inside our <Refine /> component.

Update your src/App.tsx file by adding the following lines:

src/App.tsx
import { Refine } from "@refinedev/core";

import { dataProvider } from "./providers/data-provider";

import { ShowProduct } from "./pages/products/show";
import { EditProduct } from "./pages/products/edit";
import { ListProducts } from "./pages/products/list";

export default function App(): JSX.Element {
return (
<Refine dataProvider={dataProvider}>
{/* <ShowProduct /> */}
{/* <EditProduct /> */}
<ListProducts />
</Refine>
);
}

We should be able to see the list of products on our screen now.

Adding Pagination

At this point, we've listed all the products in our API, but we're not able to paginate the list. Let's add pagination logic to our getList method.

Our fake API supports pagination through the _start and _end query parameters. _start is the index of the first record we want to fetch and _end is the index of the last record we want to fetch. So, we'll be using the pagination property to calculate the _start and _end query parameters.

Update your src/providers/data-provider.ts file by adding the following lines:

src/providers/data-provider.ts
import type { DataProvider } from "@refinedev/core";

const API_URL = "https://api.fake-rest.refine.dev";

export const dataProvider: DataProvider = {
getList: async ({ resource, pagination, filters, sorters, meta }) => {
const params = new URLSearchParams();

if (pagination) {
params.append("_start", (pagination.current - 1) * pagination.pageSize);
params.append("_end", pagination.current * pagination.pageSize);
}

const response = await fetch(`${API_URL}/${resource}?${params.toString()}`);

if (response.status < 200 || response.status > 299) throw response;

const data = await response.json();

return {
data,
total: 0, // We'll cover this in the next steps.
};
},
/* ... */
};

Now, we'll be able to paginate the list of products. Let's add pagination to our ListProducts component.

Update your src/pages/products/list.tsx file by adding the following lines:

src/pages/products/list.tsx
import { useList } from "@refinedev/core";

export const ListProducts = () => {
const { data, isLoading } = useList({
resource: "products",
pagination: { current: 1, pageSize: 10 },
});

if (isLoading) {
return <div>Loading...</div>;
}

return <div>{/* ... */}</div>;
};

We should be able to see the first 10 products on our screen now.

Adding Sorting

We've added pagination to our getList method, but we're not able to sort the list. Let's add sorting logic to our getList method.

Our fake API supports sorting through the _sort and _order query parameters. _sort is the name of the field we want to sort and _order is the order we want to sort. So, we'll be using the sorters property to calculate the _sort and _order query parameters.

Implementation Details:

Refine supports multiple sorters to be passed to the useList hook. Fortunately, our fake API also supports multiple sorters. But, if your API doesn't support multiple sorters, you can simply use the first sorter in the sorters array.

Our fake API requires multiple sorters and orders to be passed with a comma separated string. So, we'll be mapping the sorters array to a comma separated string.

Update your src/providers/data-provider.ts file by adding the following lines:

src/providers/data-provider.ts
import type { DataProvider } from "@refinedev/core";

const API_URL = "https://api.fake-rest.refine.dev";

export const dataProvider: DataProvider = {
getList: async ({ resource, pagination, filters, sorters, meta }) => {
const params = new URLSearchParams();

if (pagination) {
params.append("_start", (pagination.current - 1) * pagination.pageSize);
params.append("_end", pagination.current * pagination.pageSize);
}

if (sorters && sorters.length > 0) {
params.append("_sort", sorters.map((sorter) => sorter.field).join(","));
params.append("_order", sorters.map((sorter) => sorter.order).join(","));
}

const response = await fetch(`${API_URL}/${resource}?${params.toString()}`);

if (response.status < 200 || response.status > 299) throw response;

const data = await response.json();

return {
data,
total: 0, // We'll cover this in the next steps.
};
},
/* ... */
};

Now, we'll be able to sort the list of products. Let's add sorting to our ListProducts component.

Update your src/pages/products/list.tsx file by adding the following lines:

src/pages/products/list.tsx
import { useList } from "@refinedev/core";

export const ListProducts = () => {
const { data, isLoading } = useList({
resource: "products",
pagination: { current: 1, pageSize: 10 },
sorters: [{ field: "name", order: "asc" }],
});

if (isLoading) {
return <div>Loading...</div>;
}

return <div>{/* ... */}</div>;
};

We should be able to see the first 10 products sorted by name on our screen now.

Adding Filtering

We've added sorting to our getList method. But, we're not able to filter the list. Let's add filtering logic to our getList method.

useList's filters property implements the CrudFilters interface which accepts various operators for fields. To learn more about the operators, you can check the Filters section of the Data Fetching guide.

Implementation Details:
  • Refine supports multiple filters to be passed to the useList hook. Fortunately, our fake API also supports multiple filters. But, if your API doesn't support multiple filters, you can simply use the first filter in the filters array.

  • Our fake API supports filtering with various operators but for sake of simplicity, we'll be implementing filtering through with "eq" operator.

  • URLSearchParams's append method accepts duplicate keys and appends them to the query string. So, we'll be mapping through the filters array and appending the field name and value to the query string.

  • Refine also supports conditional filtering operators "and" and "or". But, our fake API doesn't support these operators. So, we'll be ignoring these operators in our implementation.

Update your src/providers/data-provider.ts file by adding the following lines:

src/providers/data-provider.ts
import type { DataProvider } from "@refinedev/core";

const API_URL = "https://api.fake-rest.refine.dev";

export const dataProvider: DataProvider = {
getList: async ({ resource, pagination, filters, sorters, meta }) => {
const params = new URLSearchParams();

if (pagination) {
params.append("_start", (pagination.current - 1) * pagination.pageSize);
params.append("_end", pagination.current * pagination.pageSize);
}

if (sorters && sorters.length > 0) {
params.append("_sort", sorters.map((sorter) => sorter.field).join(","));
params.append("_order", sorters.map((sorter) => sorter.order).join(","));
}

if (filters && filters.length > 0) {
filters.forEach((filter) => {
if ("field" in filter && filter.operator === "eq") {
// Our fake API supports "eq" operator by simply appending the field name and value to the query string.
params.append(filter.field, filter.value);
}
});
}

const response = await fetch(`${API_URL}/${resource}?${params.toString()}`);

if (response.status < 200 || response.status > 299) throw response;

const data = await response.json();

return {
data,
total: 0, // We'll cover this in the next steps.
};
},
/* ... */
};

Now, we'll be able to filter the list of products. Let's add filtering to our ListProducts component.

Update your src/pages/products/list.tsx file by adding the following lines:

src/pages/products/list.tsx
import { useList } from "@refinedev/core";

export const ListProducts = () => {
const { data, isLoading } = useList({
resource: "products",
pagination: { current: 1, pageSize: 10 },
sorters: [{ field: "name", order: "asc" }],
filters: [{ field: "material", operator: "eq", value: "Aluminum" }],
});

if (isLoading) {
return <div>Loading...</div>;
}

return <div>{/* ... */}</div>;
};

Summary

In this step, we've learned about the Refine's useList hook to fetch a list of records from our API. We've also learned about pagination, sorting and filtering through the useList hook.

Refine also offers useInfiniteList hook to fetch a list of records with infinite scrolling and useTable hook to fetch a list of records with additional features on top of useList hook. You can check the Hooks section of the Data Fetching guide and Tables guide to learn more about these hooks and their usages.

Implementation Tips:

To see the full implementation of a REST data provider, please check the source code of @refinedev/simple-rest.

In the next steps, we'll learn about how to handle forms and tables with Refine.

Was this helpful?
import { useOne, useUpdate } from "@refinedev/core";

export const EditProduct = () => {
  const { data, isLoading } = useOne({ resource: "products", id: 123 });
  const { mutate, isLoading: isUpdating } = useUpdate();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  const updatePrice = async () => {
    await mutate({
      resource: "products",
      id: 123,
      values: {
        price: Math.floor(Math.random() * 100),
      },
    });
  };

  return (
    <div>
      <div>Product name: {data?.data.name}</div>
      <div>Product price: ${data?.data.price}</div>
      <button onClick={updatePrice}>Update Price</button>
    </div>
  );
};
installing dependencies
installing dependencies