Refine provides an integration package for Chakra UI framework. This package provides a set of ready to use components and hooks that connects Refine with Chakra UI components. While Refine's integration offers a set of components and hooks, it is not a replacement for the Chakra UI package, you will be able to use all the features of Chakra UI in the same way you would use it in a regular React application. Refine's integration only provides components and hooks for an easier usage of Chakra UI components in combination with Refine's features and functionalities.
Dependencies: @refinedev/chakra-ui@^2.26.17,@tabler/icons@^1.119.0,@refinedev/core@^4.45.1,@refinedev/react-router-v6@^4.5.4,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@refinedev/react-hook-form@^4.8.12,@chakra-ui/react@^2.5.1,react-dom@^18.0.0,react-router@latest,react-router-dom@^6.8.1,react-hook-form@^7.30.0
Code Files File: /App.tsx
Content: import { Refine, Authenticated } from "@refinedev/core";
import {
ErrorComponent,
ThemedLayoutV2,
RefineThemes,
notificationProvider,
AuthPage
} from "@refinedev/chakra-ui";
import { ChakraProvider } from "@chakra-ui/react";
import dataProvider from "@refinedev/simple-rest";
import routerProvider, {
NavigateToResource,
} from "@refinedev/react-router-v6";
import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router-dom";
import authProvider from "./auth-provider";
import { ProductList, ProductCreate, ProductEdit, ProductShow } from "./pages/products";
const App: React.FC = () => {
return (
<BrowserRouter>
<ChakraProvider theme={RefineThemes.Blue}>
<Refine
notificationProvider={notificationProvider}
routerProvider={routerProvider}
dataProvider={dataProvider(
"https://api.fake-rest.refine.dev",
)}
authProvider={authProvider}
resources={[
{
name: "products",
list: "/products",
show: "/products/:id",
edit: "/products/:id/edit",
create: "/products/create",
meta: {
canDelete: true,
},
},
]}
>
<Routes>
<Route element={<Authenticated fallback={<Navigate to="/login" />}><Outlet /></Authenticated>}>
<Route
element={
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
}
>
<Route index element={<NavigateToResource resource="products" />} />
<Route path="/products" element={<Outlet />}>
<Route index element={<ProductList />} />
<Route path="create" element={<ProductCreate />} />
<Route path=":id" element={<ProductShow />} />
<Route path=":id/edit" element={<ProductEdit />} />
</Route>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Route>
<Route element={<Authenticated fallback={<Outlet />}><NavigateToResource resource="products" /></Authenticated>}>
<Route
path="/login"
element={(
<AuthPage
type="login"
formProps={{
defaultValues: {
email: "demo@refine.dev",
password: "demodemo",
},
}}
/>
)}
/>
<Route path="/register" element={<AuthPage type="register" />} />
<Route path="/forgot-password" element={<AuthPage type="forgotPassword" />} />
<Route path="/reset-password" element={<AuthPage type="resetPassword" />} />
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</Refine>
</ChakraProvider>
</BrowserRouter>
);
};
export default App;
File: /pages/products/index.tsx
Content: export * from "./list";
export * from "./show";
export * from "./edit";
export * from "./create";
File: /pages/products/list.tsx
Content: import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { GetManyResponse, useMany } from "@refinedev/core";
import {
List,
ShowButton,
EditButton,
DeleteButton,
DateField,
} from "@refinedev/chakra-ui";
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
HStack,
Text,
} from "@chakra-ui/react";
import { Pagination } from "../../components/pagination";
export const ProductList = () => {
const columns = React.useMemo(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "name",
header: "Name",
accessorKey: "name",
meta: {
filterOperator: "contains",
},
},
{
id: "price",
header: "Price",
accessorKey: "price",
},
{
id: "actions",
header: "Actions",
accessorKey: "id",
enableColumnFilter: false,
enableSorting: false,
cell: function render({ getValue }) {
return (
<HStack>
<ShowButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<EditButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<DeleteButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
</HStack>
);
},
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
setOptions,
refineCore: {
setCurrent,
pageCount,
current,
tableQueryResult: { data: tableData },
},
} = useTable({
columns,
refineCoreProps: {
initialSorter: [
{
field: "id",
order: "desc",
},
],
},
});
return (
<List>
<TableContainer whiteSpace="pre-line">
<Table variant="simple">
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Text>
{flexRender(
header.column.columnDef
.header,
header.getContext(),
)}
</Text>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Pagination
current={current}
pageCount={pageCount}
setCurrent={setCurrent}
/>
</List>
);
};
File: /pages/products/show.tsx
Content: import { useShow } from "@refinedev/core";
import { Show, TextField, NumberField, MarkdownField } from "@refinedev/chakra-ui";
import { Heading } from "@chakra-ui/react";
export const ProductShow = () => {
const { queryResult } = useShow();
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Heading as="h5" size="sm">
Id
</Heading>
<TextField value={record?.id} />
<Heading as="h5" size="sm" mt={4}>
Name
</Heading>
<TextField value={record?.name} />
<Heading as="h5" size="sm" mt={4}>
Material
</Heading>
<TextField value={record?.material} />
<Heading as="h5" size="sm" mt={4}>
Description
</Heading>
<MarkdownField value={record?.description} />
<Heading as="h5" size="sm" mt={4}>
Price
</Heading>
<NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} />
</Show>
);
};
File: /pages/products/edit.tsx
Content: import { Edit } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
export const ProductEdit = () => {
const {
refineCore: { formLoading, queryResult, autoSaveProps },
saveButtonProps,
register,
formState: { errors },
setValue,
} = useForm({
refineCoreProps: {
autoSave: {
enabled: true,
},
},
});
return (
<Edit
isLoading={formLoading}
saveButtonProps={saveButtonProps}
autoSaveProps={autoSaveProps}
>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Edit>
);
};
File: /pages/products/create.tsx
Content: import { Create } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
export const ProductCreate = () => {
const {
refineCore: { formLoading },
saveButtonProps,
register,
formState: { errors },
} = useForm<IPost>();
return (
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Create>
);
};
File: /components/pagination/index.tsx
Content:
import React from "react";
import { HStack, Button, Box } from "@chakra-ui/react";
import { IconChevronRight, IconChevronLeft } from "@tabler/icons";
import { usePagination } from "@refinedev/chakra-ui";
import { IconButton } from "@chakra-ui/react";
type PaginationProps = {
current: number;
pageCount: number;
setCurrent: (page: number) => void;
};
export const Pagination: React.FC<PaginationProps> = ({
current,
pageCount,
setCurrent,
}) => {
const pagination = usePagination({
current,
pageCount,
});
return (
<Box display="flex" justifyContent="flex-end">
<HStack my="3" spacing="1">
{pagination?.prev && (
<IconButton
aria-label="previous page"
onClick={() => setCurrent(current - 1)}
disabled={!pagination?.prev}
variant="outline"
>
<IconChevronLeft size="18" />
</IconButton>
)}
{pagination?.items.map((page) => {
if (typeof page === "string")
return <span key={page}>...</span>;
return (
<Button
key={page}
onClick={() => setCurrent(page)}
variant={page === current ? "solid" : "outline"}
>
{page}
</Button>
);
})}
{pagination?.next && (
<IconButton
aria-label="next page"
onClick={() => setCurrent(current + 1)}
variant="outline"
>
<IconChevronRight size="18" />
</IconButton>
)}
</HStack>
</Box>
);
};
File: /auth-provider.tsx
Content: const authProvider = {
login: async ({ username, password }) => {
(window as any).authenticated = true;
return { success: true };
},
check: async () => {
// auto login at first time
if (typeof (window as any).authenticated === "undefined") {
(window as any).authenticated = true;
}
return { authenticated: Boolean((window as any).authenticated) };
},
logout: async () => {
(window as any).authenticated = false;
return { success: true };
},
register: async () => {
return { success: true };
},
forgotPassword: async () => {
return { success: true };
},
resetPassword: async () => {
return { success: true };
},
getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"})
};
export default authProvider;
Installation Installing the package is as simple as just by running the following command without any additional configuration:
npm i @refinedev/chakra-ui @chakra-ui/react @refinedev/react-table @refinedev/react-hook-form @tanstack/react-table react-hook-form @tabler/icons@1
pnpm add @refinedev/chakra-ui @chakra-ui/react @refinedev/react-table @refinedev/react-hook-form @tanstack/react-table react-hook-form @tabler/icons@1
yarn add @refinedev/chakra-ui @chakra-ui/react @refinedev/react-table @refinedev/react-hook-form @tanstack/react-table react-hook-form @tabler/icons@1
Usage We'll wrap our app with the <ChakraProvider />
to make sure we have the theme available for our app, then we'll use the layout components to wrap them around our routes. Check out the examples below to see how to use Refine's Chakra UI integration.
React Router v6 Next.js Remix Directory pages Directory products File create.tsx File edit.tsx File index.tsx File list.tsx File show.tsx File App.tsx import { Refine , Authenticated } from "@refinedev/core" ;
import {
ErrorComponent ,
ThemedLayoutV2 ,
RefineThemes ,
notificationProvider ,
AuthPage
} from "@refinedev/chakra-ui" ;
import { ChakraProvider } from "@chakra-ui/react" ;
import dataProvider from "@refinedev/simple-rest" ;
import routerProvider , {
NavigateToResource ,
} from "@refinedev/react-router-v6" ;
import { BrowserRouter , Routes , Route , Outlet , Navigate } from "react-router-dom" ;
import authProvider from "./auth-provider" ;
import { ProductList , ProductCreate , ProductEdit , ProductShow } from "./pages/products" ;
const App : React.FC = ( ) => {
return (
< BrowserRouter >
< ChakraProvider theme ={ RefineThemes .Blue } >
< Refine
notificationProvider ={ notificationProvider }
routerProvider ={ routerProvider }
dataProvider ={ dataProvider (
"https://api.fake-rest.refine.dev" ,
) }
authProvider ={ authProvider }
resources ={ [
{
name : "products" ,
list : "/products" ,
show : "/products/:id" ,
edit : "/products/:id/edit" ,
create : "/products/create" ,
meta : {
canDelete : true ,
} ,
} ,
] }
>
< Routes >
< Route element ={ < Authenticated fallback ={ < Navigate to ="/login" /> } > < Outlet /> </ Authenticated > } >
< Route
element ={
< ThemedLayoutV2 >
< Outlet />
</ ThemedLayoutV2 >
}
>
< Route index element ={ < NavigateToResource resource ="products" /> } />
< Route path ="/products" element ={ < Outlet /> } >
< Route index element ={ < ProductList /> } />
< Route path ="create" element ={ < ProductCreate /> } />
< Route path =":id" element ={ < ProductShow /> } />
< Route path =":id/edit" element ={ < ProductEdit /> } />
</ Route >
< Route path ="*" element ={ < ErrorComponent /> } />
</ Route >
</ Route >
< Route element ={ < Authenticated fallback ={ < Outlet /> } > < NavigateToResource resource ="products" /> </ Authenticated > } >
< Route
path ="/login"
element ={ (
< AuthPage
type ="login"
formProps ={ {
defaultValues : {
email : "demo@refine.dev" ,
password : "demodemo" ,
} ,
} }
/>
) }
/>
< Route path ="/register" element ={ < AuthPage type ="register" /> } />
< Route path ="/forgot-password" element ={ < AuthPage type ="forgotPassword" /> } />
< Route path ="/reset-password" element ={ < AuthPage type ="resetPassword" /> } />
< Route path ="*" element ={ < ErrorComponent /> } />
</ Route >
</ Routes >
</ Refine >
</ ChakraProvider >
</ BrowserRouter >
) ;
} ;
export default App ;
Dependencies: @refinedev/chakra-ui@^2.26.17,@tabler/icons@^1.119.0,@refinedev/core@^4.45.1,@refinedev/react-router-v6@^4.5.4,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@refinedev/react-hook-form@^4.8.12,@chakra-ui/react@^2.5.1,react-dom@^18.0.0,react-router-dom@^6.8.1
Code Files File: /App.tsx
Content: import { Refine, Authenticated } from "@refinedev/core";
import {
ErrorComponent,
ThemedLayoutV2,
RefineThemes,
notificationProvider,
AuthPage
} from "@refinedev/chakra-ui";
import { ChakraProvider } from "@chakra-ui/react";
import dataProvider from "@refinedev/simple-rest";
import routerProvider, {
NavigateToResource,
} from "@refinedev/react-router-v6";
import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router-dom";
import authProvider from "./auth-provider";
import { ProductList, ProductCreate, ProductEdit, ProductShow } from "./pages/products";
const App: React.FC = () => {
return (
<BrowserRouter>
<ChakraProvider theme={RefineThemes.Blue}>
<Refine
notificationProvider={notificationProvider}
routerProvider={routerProvider}
dataProvider={dataProvider(
"https://api.fake-rest.refine.dev",
)}
authProvider={authProvider}
resources={[
{
name: "products",
list: "/products",
show: "/products/:id",
edit: "/products/:id/edit",
create: "/products/create",
meta: {
canDelete: true,
},
},
]}
>
<Routes>
<Route element={<Authenticated fallback={<Navigate to="/login" />}><Outlet /></Authenticated>}>
<Route
element={
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
}
>
<Route index element={<NavigateToResource resource="products" />} />
<Route path="/products" element={<Outlet />}>
<Route index element={<ProductList />} />
<Route path="create" element={<ProductCreate />} />
<Route path=":id" element={<ProductShow />} />
<Route path=":id/edit" element={<ProductEdit />} />
</Route>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Route>
<Route element={<Authenticated fallback={<Outlet />}><NavigateToResource resource="products" /></Authenticated>}>
<Route
path="/login"
element={(
<AuthPage
type="login"
formProps={{
defaultValues: {
email: "demo@refine.dev",
password: "demodemo",
},
}}
/>
)}
/>
<Route path="/register" element={<AuthPage type="register" />} />
<Route path="/forgot-password" element={<AuthPage type="forgotPassword" />} />
<Route path="/reset-password" element={<AuthPage type="resetPassword" />} />
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</Refine>
</ChakraProvider>
</BrowserRouter>
);
};
export default App;
File: /pages/products/index.tsx
Content: export * from "./list";
export * from "./show";
export * from "./edit";
export * from "./create";
File: /pages/products/list.tsx
Content: import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { GetManyResponse, useMany } from "@refinedev/core";
import {
List,
ShowButton,
EditButton,
DeleteButton,
DateField,
} from "@refinedev/chakra-ui";
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
HStack,
Text,
} from "@chakra-ui/react";
import { Pagination } from "../../components/pagination";
export const ProductList = () => {
const columns = React.useMemo(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "name",
header: "Name",
accessorKey: "name",
meta: {
filterOperator: "contains",
},
},
{
id: "price",
header: "Price",
accessorKey: "price",
},
{
id: "actions",
header: "Actions",
accessorKey: "id",
enableColumnFilter: false,
enableSorting: false,
cell: function render({ getValue }) {
return (
<HStack>
<ShowButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<EditButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<DeleteButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
</HStack>
);
},
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
setOptions,
refineCore: {
setCurrent,
pageCount,
current,
tableQueryResult: { data: tableData },
},
} = useTable({
columns,
refineCoreProps: {
initialSorter: [
{
field: "id",
order: "desc",
},
],
},
});
return (
<List>
<TableContainer whiteSpace="pre-line">
<Table variant="simple">
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Text>
{flexRender(
header.column.columnDef
.header,
header.getContext(),
)}
</Text>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Pagination
current={current}
pageCount={pageCount}
setCurrent={setCurrent}
/>
</List>
);
};
File: /pages/products/show.tsx
Content: import { useShow } from "@refinedev/core";
import { Show, TextField, NumberField, MarkdownField } from "@refinedev/chakra-ui";
import { Heading } from "@chakra-ui/react";
export const ProductShow = () => {
const { queryResult } = useShow();
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Heading as="h5" size="sm">
Id
</Heading>
<TextField value={record?.id} />
<Heading as="h5" size="sm" mt={4}>
Name
</Heading>
<TextField value={record?.name} />
<Heading as="h5" size="sm" mt={4}>
Material
</Heading>
<TextField value={record?.material} />
<Heading as="h5" size="sm" mt={4}>
Description
</Heading>
<MarkdownField value={record?.description} />
<Heading as="h5" size="sm" mt={4}>
Price
</Heading>
<NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} />
</Show>
);
};
File: /pages/products/edit.tsx
Content: import { Edit } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
export const ProductEdit = () => {
const {
refineCore: { formLoading, queryResult, autoSaveProps },
saveButtonProps,
register,
formState: { errors },
setValue,
} = useForm({
refineCoreProps: {
autoSave: {
enabled: true,
},
},
});
return (
<Edit
isLoading={formLoading}
saveButtonProps={saveButtonProps}
autoSaveProps={autoSaveProps}
>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Edit>
);
};
File: /pages/products/create.tsx
Content: import { Create } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
export const ProductCreate = () => {
const {
refineCore: { formLoading },
saveButtonProps,
register,
formState: { errors },
} = useForm<IPost>();
return (
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Create>
);
};
File: /components/pagination/index.tsx
Content:
import React from "react";
import { HStack, Button, Box } from "@chakra-ui/react";
import { IconChevronRight, IconChevronLeft } from "@tabler/icons";
import { usePagination } from "@refinedev/chakra-ui";
import { IconButton } from "@chakra-ui/react";
type PaginationProps = {
current: number;
pageCount: number;
setCurrent: (page: number) => void;
};
export const Pagination: React.FC<PaginationProps> = ({
current,
pageCount,
setCurrent,
}) => {
const pagination = usePagination({
current,
pageCount,
});
return (
<Box display="flex" justifyContent="flex-end">
<HStack my="3" spacing="1">
{pagination?.prev && (
<IconButton
aria-label="previous page"
onClick={() => setCurrent(current - 1)}
disabled={!pagination?.prev}
variant="outline"
>
<IconChevronLeft size="18" />
</IconButton>
)}
{pagination?.items.map((page) => {
if (typeof page === "string")
return <span key={page}>...</span>;
return (
<Button
key={page}
onClick={() => setCurrent(page)}
variant={page === current ? "solid" : "outline"}
>
{page}
</Button>
);
})}
{pagination?.next && (
<IconButton
aria-label="next page"
onClick={() => setCurrent(current + 1)}
variant="outline"
>
<IconChevronRight size="18" />
</IconButton>
)}
</HStack>
</Box>
);
};
File: /auth-provider.tsx
Content: const authProvider = {
login: async ({ username, password }) => {
(window as any).authenticated = true;
return { success: true };
},
check: async () => {
// auto login at first time
if (typeof (window as any).authenticated === "undefined") {
(window as any).authenticated = true;
}
return { authenticated: Boolean((window as any).authenticated) };
},
logout: async () => {
(window as any).authenticated = false;
return { success: true };
},
register: async () => {
return { success: true };
},
forgotPassword: async () => {
return { success: true };
},
resetPassword: async () => {
return { success: true };
},
getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"})
};
export default authProvider;
Directory pages Directory products File [id].tsx File create.tsx File index.tsx File _app.tsx File login.tsx import React from "react" ;
import { Refine } from "@refinedev/core" ;
import routerProvider from "@refinedev/nextjs-router/pages" ;
import dataProvider from "@refinedev/simple-rest" ;
import type { AppProps } from "next/app" ;
import { RefineThemes , ThemedLayoutV2 , notificationProvider } from "@refinedev/chakra-ui" ;
import { ChakraProvider } from "@chakra-ui/react" ;
import authProvider from "../src/auth-provider" ;
export type ExtendedNextPage = NextPage & {
noLayout ?: boolean;
} ;
type ExtendedAppProps = AppProps & {
Component : ExtendedNextPage;
} ;
function App ( { Component , pageProps } : ExtendedAppProps) {
const renderComponent = ( ) => {
if ( Component .noLayout ) {
return < Component { ... pageProps } /> ;
}
return (
< ThemedLayoutV2 >
< Component { ... pageProps } />
</ ThemedLayoutV2 >
) ;
}
return (
< ChakraProvider theme ={ RefineThemes .Blue } >
< Refine
routerProvider ={ routerProvider }
dataProvider ={ dataProvider ( "https://api.fake-rest.refine.dev" ) }
notificationProvider ={ notificationProvider }
authProvider ={ authProvider }
resources ={ [
{
name : "products" ,
list : "/products" ,
show : "/products/:id" ,
edit : "/products/:id/edit" ,
create : "/products/create"
} ,
] }
options ={ { syncWithLocation : true } }
>
{ renderComponent ( ) }
</ Refine >
</ ChakraProvider >
) ;
}
export default App ;
Dependencies: @refinedev/chakra-ui@^2.26.17,@tabler/icons@^1.119.0,@refinedev/core@^4.45.1,@refinedev/react-router-v6@^4.5.4,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@refinedev/react-hook-form@^4.8.12,@chakra-ui/react@^2.5.1,@refinedev/nextjs-router@latest
Code Files File: /pages/_app.tsx
Content: import React from "react";
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/nextjs-router/pages";
import dataProvider from "@refinedev/simple-rest";
import type { AppProps } from "next/app";
import { RefineThemes, ThemedLayoutV2, notificationProvider } from "@refinedev/chakra-ui";
import { ChakraProvider } from "@chakra-ui/react";
import authProvider from "../src/auth-provider";
export type ExtendedNextPage = NextPage & {
noLayout?: boolean;
};
type ExtendedAppProps = AppProps & {
Component: ExtendedNextPage;
};
function App({ Component, pageProps }: ExtendedAppProps) {
const renderComponent = () => {
if (Component.noLayout) {
return <Component {...pageProps} />;
}
return (
<ThemedLayoutV2>
<Component {...pageProps} />
</ThemedLayoutV2>
);
}
return (
<ChakraProvider theme={RefineThemes.Blue}>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
notificationProvider={notificationProvider}
authProvider={authProvider}
resources={[
{
name: "products",
list: "/products",
show: "/products/:id",
edit: "/products/:id/edit",
create: "/products/create"
},
]}
options={{ syncWithLocation: true }}
>
{renderComponent()}
</Refine>
</ChakraProvider>
);
}
export default App;
File: /pages/products/index.tsx
Content: import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { GetManyResponse, useMany } from "@refinedev/core";
import {
List,
ShowButton,
EditButton,
DeleteButton,
DateField,
} from "@refinedev/chakra-ui";
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
HStack,
Text,
} from "@chakra-ui/react";
import authProvider from "../../src/auth-provider";
import { Pagination } from "../../src/components/pagination";
export default function ProductList() {
const columns = React.useMemo(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "name",
header: "Name",
accessorKey: "name",
meta: {
filterOperator: "contains",
},
},
{
id: "price",
header: "Price",
accessorKey: "price",
},
{
id: "actions",
header: "Actions",
accessorKey: "id",
enableColumnFilter: false,
enableSorting: false,
cell: function render({ getValue }) {
return (
<HStack>
<ShowButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<EditButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<DeleteButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
</HStack>
);
},
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
setOptions,
refineCore: {
setCurrent,
pageCount,
current,
tableQueryResult: { data: tableData },
},
} = useTable({
columns,
refineCoreProps: {
initialSorter: [
{
field: "id",
order: "desc",
},
],
},
});
return (
<List>
<TableContainer whiteSpace="pre-line">
<Table variant="simple">
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Text>
{flexRender(
header.column.columnDef
.header,
header.getContext(),
)}
</Text>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Pagination
current={current}
pageCount={pageCount}
setCurrent={setCurrent}
/>
</List>
);
};
/**
* Same check can also be done via `<Authenticated />` component.
* But we're using a server-side check for a better UX.
*/
export const getServerSideProps = async () => {
const { authenticated } = await authProvider.check();
if (!authenticated) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}
return {
props: {},
};
}
interface IProduct {
id: string;
name: string;
price: number;
description: string;
}
File: /pages/products/[id].tsx
Content: import { useShow } from "@refinedev/core";
import { Show, TextField, NumberField, MarkdownField } from "@refinedev/chakra-ui";
import { Heading } from "@chakra-ui/react";
import authProvider from "../../src/auth-provider";
export default function ProductShow() {
const { queryResult } = useShow();
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Heading as="h5" size="sm">
Id
</Heading>
<TextField value={record?.id} />
<Heading as="h5" size="sm" mt={4}>
Name
</Heading>
<TextField value={record?.name} />
<Heading as="h5" size="sm" mt={4}>
Material
</Heading>
<TextField value={record?.material} />
<Heading as="h5" size="sm" mt={4}>
Description
</Heading>
<MarkdownField value={record?.description} />
<Heading as="h5" size="sm" mt={4}>
Price
</Heading>
<NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} />
</Show>
);
};
/**
* Same check can also be done via `<Authenticated />` component.
* But we're using a server-side check for a better UX.
*/
export const getServerSideProps = async () => {
const { authenticated } = await authProvider.check();
if (!authenticated) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}
return {
props: {},
};
}
File: /pages/products/[id]/edit.tsx
Content: import { Edit } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
import authProvider from "../../../src/auth-provider";
export default function ProductEdit() {
const {
refineCore: { formLoading, queryResult, autoSaveProps },
saveButtonProps,
register,
formState: { errors },
setValue,
} = useForm({
refineCoreProps: {
autoSave: {
enabled: true,
},
},
});
return (
<Edit
isLoading={formLoading}
saveButtonProps={saveButtonProps}
autoSaveProps={autoSaveProps}
>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Edit>
);
};
/**
* Same check can also be done via `<Authenticated />` component.
* But we're using a server-side check for a better UX.
*/
export const getServerSideProps = async () => {
const { authenticated } = await authProvider.check();
if (!authenticated) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}
return {
props: {},
};
}
File: /pages/products/create.tsx
Content: import { Create } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
import authProvider from "../../src/auth-provider";
export default function ProductCreate() {
const {
refineCore: { formLoading },
saveButtonProps,
register,
formState: { errors },
} = useForm<IPost>();
return (
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Create>
);
};
/**
* Same check can also be done via `<Authenticated />` component.
* But we're using a server-side check for a better UX.
*/
export const getServerSideProps = async () => {
const { authenticated } = await authProvider.check();
if (!authenticated) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}
return {
props: {},
};
}
File: /pages/login.tsx
Content: import React from "react";
import { AuthPage } from "@refinedev/chakra-ui";
import authProvider from "../src/auth-provider";
import type { ExtendedNextPage } from "./_app";
const Login: ExtendedNextPage = () => {
return <AuthPage type="login" />;
};
Login.noLayout = true;
export default Login;
/**
* Same check can also be done via `<Authenticated />` component.
* But we're using a server-side check for a better UX.
*/
export const getServerSideProps = async () => {
const { authenticated } = await authProvider.check();
if (authenticated) {
return {
redirect: {
destination: "/products",
permanent: false,
},
};
}
return {
props: {},
};
File: /src/components/pagination/index.tsx
Content:
import React from "react";
import { HStack, Button, Box } from "@chakra-ui/react";
import { IconChevronRight, IconChevronLeft } from "@tabler/icons";
import { usePagination } from "@refinedev/chakra-ui";
import { IconButton } from "@chakra-ui/react";
type PaginationProps = {
current: number;
pageCount: number;
setCurrent: (page: number) => void;
};
export const Pagination: React.FC<PaginationProps> = ({
current,
pageCount,
setCurrent,
}) => {
const pagination = usePagination({
current,
pageCount,
});
return (
<Box display="flex" justifyContent="flex-end">
<HStack my="3" spacing="1">
{pagination?.prev && (
<IconButton
aria-label="previous page"
onClick={() => setCurrent(current - 1)}
disabled={!pagination?.prev}
variant="outline"
>
<IconChevronLeft size="18" />
</IconButton>
)}
{pagination?.items.map((page) => {
if (typeof page === "string")
return <span key={page}>...</span>;
return (
<Button
key={page}
onClick={() => setCurrent(page)}
variant={page === current ? "solid" : "outline"}
>
{page}
</Button>
);
})}
{pagination?.next && (
<IconButton
aria-label="next page"
onClick={() => setCurrent(current + 1)}
variant="outline"
>
<IconChevronRight size="18" />
</IconButton>
)}
</HStack>
</Box>
);
};
File: /src/auth-provider.tsx
Content: const authProvider = {
login: async ({ username, password }) => {
(window as any).authenticated = true;
return { success: true };
},
check: async () => {
// auto login at first time
if (typeof (window as any).authenticated === "undefined") {
(window as any).authenticated = true;
}
return { authenticated: Boolean((window as any).authenticated) };
},
logout: async () => {
(window as any).authenticated = false;
return { success: true };
},
register: async () => {
return { success: true };
},
forgotPassword: async () => {
return { success: true };
},
resetPassword: async () => {
return { success: true };
},
getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"})
};
export default authProvider;
Directory app Directory routes File _auth.login.tsx File _auth.tsx File _protected.products.$id.edit.tsx File _protected.products.$id.tsx File _protected.products._index.tsx File _protected.products.create.tsx File _protected.tsx File root.tsx import React from "react" ;
import {
Links ,
LiveReload ,
Meta ,
Outlet ,
Scripts ,
ScrollRestoration ,
} from "@remix-run/react" ;
import { Refine } from "@refinedev/core" ;
import routerProvider from "@refinedev/remix-router" ;
import dataProvider from "@refinedev/simple-rest" ;
import { notificationProvider , RefineThemes } from "@refinedev/chakra-ui" ;
import { ChakraProvider } from "@chakra-ui/react" ;
import authProvider from "./auth-provider" ;
export default function App ( ) {
return (
< html lang ="en" >
< head >
< Meta />
< Links />
</ head >
< body >
< ChakraProvider theme ={ RefineThemes .Blue } >
< Refine
routerProvider ={ routerProvider }
dataProvider ={ dataProvider ( "https://api.fake-rest.refine.dev" ) }
authProvider ={ authProvider }
notificationProvider ={ notificationProvider }
resources ={ [
{
name : "products" ,
list : "/products" ,
show : "/products/:id" ,
edit : "/products/:id/edit" ,
create : "/products/create" ,
} ,
] }
options ={ { syncWithLocation : true } }
>
< Outlet />
</ Refine >
</ ChakraProvider >
< ScrollRestoration />
< Scripts />
< LiveReload />
</ body >
</ html >
) ;
}
Dependencies: @refinedev/chakra-ui@^2.26.17,@tabler/icons@^1.119.0,@refinedev/core@^4.45.1,@refinedev/react-router-v6@^4.5.4,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@refinedev/react-hook-form@^4.8.12,@chakra-ui/react@^2.5.1,@refinedev/remix-router@latest
Code Files File: /app/root.tsx
Content: import React from "react";
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/remix-router";
import dataProvider from "@refinedev/simple-rest";
import { notificationProvider, RefineThemes } from "@refinedev/chakra-ui";
import { ChakraProvider } from "@chakra-ui/react";
import authProvider from "./auth-provider";
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<ChakraProvider theme={RefineThemes.Blue}>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
authProvider={authProvider}
notificationProvider={notificationProvider}
resources={[
{
name: "products",
list: "/products",
show: "/products/:id",
edit: "/products/:id/edit",
create: "/products/create",
},
]}
options={{ syncWithLocation: true }}
>
<Outlet />
</Refine>
</ChakraProvider>
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
File: /app/routes/_protected.tsx
Content: import { ThemedLayoutV2 } from "@refinedev/chakra-ui";
import { Outlet } from "@remix-run/react";
import { LoaderArgs, redirect } from "@remix-run/node";
import authProvider from "../auth-provider";
export default function AuthenticatedLayout() {
// `<ThemedLayoutV2>` is only applied to the authenticated users
return (
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
);
}
/**
* We're checking if the current session is authenticated.
* If not, we're redirecting the user to the login page.
* This is applied for all routes that are nested under this layout (_protected).
*/
export async function loader({ request }: LoaderArgs) {
const { authenticated, redirectTo } = await authProvider.check(request);
if (!authenticated) {
throw redirect(redirectTo ?? "/login");
}
return {};
}
File: /app/routes/_protected.products._index.tsx
Content: import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { GetManyResponse, useMany } from "@refinedev/core";
import {
List,
ShowButton,
EditButton,
DeleteButton,
DateField,
} from "@refinedev/chakra-ui";
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
HStack,
Text,
} from "@chakra-ui/react";
import { Pagination } from "~/components/pagination";
export default function ProductList() {
const columns = React.useMemo(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "name",
header: "Name",
accessorKey: "name",
meta: {
filterOperator: "contains",
},
},
{
id: "price",
header: "Price",
accessorKey: "price",
},
{
id: "actions",
header: "Actions",
accessorKey: "id",
enableColumnFilter: false,
enableSorting: false,
cell: function render({ getValue }) {
return (
<HStack>
<ShowButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<EditButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<DeleteButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
</HStack>
);
},
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
setOptions,
refineCore: {
setCurrent,
pageCount,
current,
tableQueryResult: { data: tableData },
},
} = useTable({
columns,
refineCoreProps: {
initialSorter: [
{
field: "id",
order: "desc",
},
],
},
});
return (
<List>
<TableContainer whiteSpace="pre-line">
<Table variant="simple">
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Text>
{flexRender(
header.column.columnDef
.header,
header.getContext(),
)}
</Text>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Pagination
current={current}
pageCount={pageCount}
setCurrent={setCurrent}
/>
</List>
);
};
File: /app/routes/_protected.products.$id.tsx
Content: import { useShow } from "@refinedev/core";
import { Show, TextField, NumberField, MarkdownField } from "@refinedev/chakra-ui";
import { Heading } from "@chakra-ui/react";
export default function ProductShow() {
const { queryResult } = useShow();
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Heading as="h5" size="sm">
Id
</Heading>
<TextField value={record?.id} />
<Heading as="h5" size="sm" mt={4}>
Name
</Heading>
<TextField value={record?.name} />
<Heading as="h5" size="sm" mt={4}>
Material
</Heading>
<TextField value={record?.material} />
<Heading as="h5" size="sm" mt={4}>
Description
</Heading>
<MarkdownField value={record?.description} />
<Heading as="h5" size="sm" mt={4}>
Price
</Heading>
<NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} />
</Show>
);
};
File: /app/routes/_protected.products.$id.edit.tsx
Content: import { Edit } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
export default function ProductEdit() {
const {
refineCore: { formLoading, queryResult, autoSaveProps },
saveButtonProps,
register,
formState: { errors },
setValue,
} = useForm({
refineCoreProps: {
autoSave: {
enabled: true,
},
},
});
return (
<Edit
isLoading={formLoading}
saveButtonProps={saveButtonProps}
autoSaveProps={autoSaveProps}
>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Edit>
);
};
File: /app/routes/_protected.products.create.tsx
Content: import { Create } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
export default function ProductCreate() {
const {
refineCore: { formLoading },
saveButtonProps,
register,
formState: { errors },
} = useForm<IPost>();
return (
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Create>
);
};
File: /app/routes/_auth.tsx
Content: import { Outlet } from "@remix-run/react";
import { LoaderArgs, redirect } from "@remix-run/node";
import { authProvider } from "~/authProvider";
export default function AuthLayout() {
// no layout is applied for the auth routes
return <Outlet />;
}
/**
* If the current session is authenticated, we're redirecting the user to the home page.
* Alternatively, we could also use the `Authenticated` component inside the `AuthLayout` to handle the redirect.
* But, server-side redirects are more performant.
*/
export async function loader({ request }: LoaderArgs) {
const { authenticated, redirectTo } = await authProvider.check(request);
if (authenticated) {
throw redirect(redirectTo ?? "/");
}
return {};
}
File: /app/routes/_auth.login.tsx
Content: import { AuthPage } from "@refinedev/chakra-ui";
export default function LoginPage() {
return <AuthPage type="login" />;
}
File: /app/auth-provider.tsx
Content: const authProvider = {
login: async ({ username, password }) => {
(window as any).authenticated = true;
return { success: true };
},
check: async () => {
// auto login at first time
if (typeof (window as any).authenticated === "undefined") {
(window as any).authenticated = true;
}
return { authenticated: Boolean((window as any).authenticated) };
},
logout: async () => {
(window as any).authenticated = false;
return { success: true };
},
register: async () => {
return { success: true };
},
forgotPassword: async () => {
return { success: true };
},
resetPassword: async () => {
return { success: true };
},
getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"})
};
export default authProvider;
Tables Chakra UI offers styled table primitives but lacks the table management solution. Refine recommends using @refinedev/react-table
package which is built on top of Refine's useTable
hook and Tanstack Table's useTable
hook to enable features from pagination to sorting and filtering. Refine's documentations and examples of Chakra UI uses @refinedev/react-table
package for table management but you have the option to use any table management solution you want.
import React from "react" ; import { useTable } from "@refinedev/react-table" ; import { ColumnDef , flexRender } from "@tanstack/react-table" ; import { GetManyResponse , useMany } from "@refinedev/core" ; import { List , ShowButton , EditButton , DeleteButton , DateField , } from "@refinedev/chakra-ui" ; import { Table , Thead , Tbody , Tr , Th , Td , TableContainer , HStack , Text , } from "@chakra-ui/react" ; import { Pagination } from "../../components/pagination" ; const columns = [ { id : "id" , header : "ID" , accessorKey : "id" } , { id : "name" , header : "Name" , accessorKey : "name" , meta : { filterOperator : "contains" } , } , { id : "price" , header : "Price" , accessorKey : "price" } , { id : "actions" , header : "Actions" , accessorKey : "id" , enableColumnFilter : false , enableSorting : false , cell : function render ( { getValue } ) { return ( < HStack > < ShowButton hideText size = " sm " recordItemId = { getValue ( ) as number } /> < EditButton hideText size = " sm " recordItemId = { getValue ( ) as number } /> < DeleteButton hideText size = " sm " recordItemId = { getValue ( ) as number } /> </ HStack > ) ; } , } , ] ; export const ProductList = ( ) => { const { getHeaderGroups , getRowModel , setOptions , refineCore : { setCurrent , pageCount , current , tableQueryResult : { data : tableData } , } , } = useTable < IProduct > ( { columns , refineCoreProps : { initialSorter : [ { field : "id" , order : "desc" , } , ] , } , } ) ; return ( < List > < TableContainer whiteSpace = " pre-line " > < Table variant = " simple " > < Thead > { getHeaderGroups ( ) . map ( ( headerGroup ) => ( < Tr key = { headerGroup . id } > { headerGroup . headers . map ( ( header ) => ( < Th key = { header . id } > < Text > { flexRender ( header . column . columnDef . header , header . getContext ( ) , ) } </ Text > </ Th > ) ) } </ Tr > ) ) } </ Thead > < Tbody > { getRowModel ( ) . rows . map ( ( row ) => ( < Tr key = { row . id } > { row . getVisibleCells ( ) . map ( ( cell ) => ( < Td key = { cell . id } > { flexRender ( cell . column . columnDef . cell , cell . getContext ( ) ) } </ Td > ) ) } </ Tr > ) ) } </ Tbody > </ Table > </ TableContainer > < Pagination current = { current } pageCount = { pageCount } setCurrent = { setCurrent } /> </ List > ) ; } ; interface IProduct { id : string ; name : string ; price : number ; description : string ; }
<Pagination />
component is a custom component that is used to render the pagination controls which uses usePagination
hook from @refinedev/chakra-ui
package. This hook accepts the pagination values from useTable
hook and returns the pagination controls and related props.
Pagination Component components/pagination.tsx
import React from "react" ; import { HStack , Button , Box } from "@chakra-ui/react" ; import { IconChevronRight , IconChevronLeft } from "@tabler/icons" ; import { usePagination } from "@refinedev/chakra-ui" ; import { IconButton } from "@chakra-ui/react" ; type PaginationProps = { current : number ; pageCount : number ; setCurrent : ( page : number ) => void ; } ; export const Pagination : React . FC < PaginationProps > = ( { current , pageCount , setCurrent , } ) => { const pagination = usePagination ( { current , pageCount , } ) ; return ( < Box display = " flex " justifyContent = " flex-end " > < HStack my = " 3 " spacing = " 1 " > { pagination ?. prev && ( < IconButton aria-label = " previous page " onClick = { ( ) => setCurrent ( current - 1 ) } disabled = { ! pagination ?. prev } variant = " outline " > < IconChevronLeft size = " 18 " /> </ IconButton > ) } { pagination ?. items . map ( ( page ) => { if ( typeof page === "string" ) return < span key = { page } > ... </ span > ; return ( < Button key = { page } onClick = { ( ) => setCurrent ( page ) } variant = { page === current ? "solid" : "outline" } > { page } </ Button > ) ; } ) } { pagination ?. next && ( < IconButton aria-label = " next page " onClick = { ( ) => setCurrent ( current + 1 ) } variant = " outline " > < IconChevronRight size = " 18 " /> </ IconButton > ) } </ HStack > </ Box > ) ; } ;
Chakra UI offers form elements yet it does not provide a form management solution. To have a complete solution, Refine recommends using @refinedev/react-hook-form
package which is built on top of Refine's useForm
hook and React Hook Form's useForm
hook.
Refine's documentations and examples of Chakra UI uses @refinedev/react-hook-form
package for form management but you have the option to use any form management solution you want.
pages/products/create.tsx
import { Create } from "@refinedev/chakra-ui" ; import { FormControl , FormErrorMessage , FormLabel , Input , Textarea , } from "@chakra-ui/react" ; import { useForm } from "@refinedev/react-hook-form" ; export const ProductCreate = ( ) => { const { refineCore : { formLoading } , saveButtonProps , register , formState : { errors } , } = useForm < IPost > ( ) ; return ( < Create isLoading = { formLoading } saveButtonProps = { saveButtonProps } > < FormControl mb = " 3 " isInvalid = { ! ! errors ?. name } > < FormLabel > Name </ FormLabel > < Input id = " name " type = " text " { ... register ( "name" , { required : "Name is required" } ) } /> < FormErrorMessage > { ` ${ errors . name ?. message } ` } </ FormErrorMessage > </ FormControl > < FormControl mb = " 3 " isInvalid = { ! ! errors ?. material } > < FormLabel > Material </ FormLabel > < Input id = " material " type = " text " { ... register ( "material" , { required : "Material is required" } ) } /> < FormErrorMessage > { \`$\ { errors . material ?. message } \` } </ FormErrorMessage > </ FormControl > < FormControl mb = " 3 " isInvalid = { ! ! errors ?. description } > < FormLabel > Description </ FormLabel > < Textarea id = " description " { ... register ( "description" , { required : "Description is required" , } ) } /> < FormErrorMessage > { \`$\ { errors . description ?. message } \` } </ FormErrorMessage > </ FormControl > < FormControl mb = " 3 " isInvalid = { ! ! errors ?. price } > < FormLabel > Price </ FormLabel > < Input id = " price " type = " number " { ... register ( "price" , { required : "Price is required" } ) } /> < FormErrorMessage > { \`$\ { errors . price ?. message } \` } </ FormErrorMessage > </ FormControl > </ Create > ) ; } ;
Additional hooks of @refinedev/react-hook-form
such as useStepsForm
and useModalForm
can also be used together with Refine's Chakra UI integration with ease.
Notifications Chakra UI has its own notification system which works seamlessly with its UI elements. Refine also provides a seamless integration with Chakra UI's notification system and show notifications for related actions and events. This integration is provided by the notificationProvider
hook exported from the @refinedev/chakra-ui
package which can be directly used in the notificationProvider
prop of the <Refine />
component.
import { Refine } from "@refinedev/core" ; import { useNotificationProvider } from "@refinedev/chakra-ui" ; const App = ( ) => { return ( < Refine notificationProvider = { useNotificationProvider } > { } </ Refine > ) ; } ;
Predefined Components and Views Layouts, Menus and Breadcrumbs Refine provides Layout components that can be used to implement a layout for the application. These components are crafted using Chakra UI's components and includes Refine's features and functionalities such as navigation menus, headers, authentication, authorization and more.
React Router v6 Next.js Remix import React from "react" ;
import { Refine , Authenticated } from "@refinedev/core" ;
import dataProvider from "@refinedev/simple-rest" ;
import routerProvider from "@refinedev/react-router-v6" ;
import { BrowserRouter , Route , Routes , Outlet } from "react-router-dom" ;
import {
ThemedLayoutV2 ,
ErrorComponent ,
RefineThemes ,
notificationProvider ,
} from "@refinedev/chakra-ui" ;
import { ChakraProvider } from "@chakra-ui/react" ;
import { ProductList } from "./pages/products/list" ;
export default function App ( ) {
return (
< BrowserRouter >
< ChakraProvider theme ={ RefineThemes .Blue } >
< Refine
routerProvider ={ routerProvider }
dataProvider ={ dataProvider ( "https://api.fake-rest.refine.dev" ) }
notificationProvider ={ notificationProvider }
resources ={ [
{
name : "products" ,
list : "/products" ,
}
] }
>
< Routes >
< Route
element ={
< ThemedLayoutV2 >
< Outlet />
</ ThemedLayoutV2 >
}
>
< Route path ="/products" element ={ < ProductList /> } />
< Route path ="*" element ={ < ErrorComponent /> } />
</ Route >
</ Routes >
</ Refine >
</ ChakraProvider >
</ BrowserRouter >
) ;
} ;
Dependencies: @refinedev/chakra-ui@^2.26.17,@tabler/icons@^1.119.0,@refinedev/core@^4.45.1,@refinedev/react-router-v6@^4.5.4,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@refinedev/react-hook-form@^4.8.12,@chakra-ui/react@^2.5.1,react-dom@^18.0.0,react-router@latest,react-router-dom@^6.8.1,react-hook-form@^7.30.0
Code Files File: /App.tsx
Content: import React from "react";
import { Refine, Authenticated } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/react-router-v6";
import { BrowserRouter, Route, Routes, Outlet } from "react-router-dom";
import {
ThemedLayoutV2,
ErrorComponent,
RefineThemes,
notificationProvider,
} from "@refinedev/chakra-ui";
import { ChakraProvider } from "@chakra-ui/react";
import { ProductList } from "./pages/products/list";
export default function App() {
return (
<BrowserRouter>
<ChakraProvider theme={RefineThemes.Blue}>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
notificationProvider={notificationProvider}
resources={[
{
name: "products",
list: "/products",
}
]}
>
<Routes>
<Route
// The layout will wrap all the pages inside this route
element={
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
}
>
<Route path="/products" element={<ProductList />} />
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</Refine>
</ChakraProvider>
</BrowserRouter>
);
};
File: /pages/products/list.tsx
Content: import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { GetManyResponse, useMany } from "@refinedev/core";
import {
List,
DateField,
} from "@refinedev/chakra-ui";
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
HStack,
Text,
} from "@chakra-ui/react";
import { Pagination } from "../../components/pagination";
export const ProductList = () => {
const columns = React.useMemo(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "name",
header: "Name",
accessorKey: "name",
meta: {
filterOperator: "contains",
},
},
{
id: "price",
header: "Price",
accessorKey: "price",
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
setOptions,
refineCore: {
setCurrent,
pageCount,
current,
tableQueryResult: { data: tableData },
},
} = useTable({
columns,
refineCoreProps: {
initialSorter: [
{
field: "id",
order: "desc",
},
],
},
});
return (
<List>
<TableContainer whiteSpace="pre-line">
<Table variant="simple">
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Text>
{flexRender(
header.column.columnDef
.header,
header.getContext(),
)}
</Text>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Pagination
current={current}
pageCount={pageCount}
setCurrent={setCurrent}
/>
</List>
);
};
File: /components/pagination/index.tsx
Content:
import React from "react";
import { HStack, Button, Box } from "@chakra-ui/react";
import { IconChevronRight, IconChevronLeft } from "@tabler/icons";
import { usePagination } from "@refinedev/chakra-ui";
import { IconButton } from "@chakra-ui/react";
type PaginationProps = {
current: number;
pageCount: number;
setCurrent: (page: number) => void;
};
export const Pagination: React.FC<PaginationProps> = ({
current,
pageCount,
setCurrent,
}) => {
const pagination = usePagination({
current,
pageCount,
});
return (
<Box display="flex" justifyContent="flex-end">
<HStack my="3" spacing="1">
{pagination?.prev && (
<IconButton
aria-label="previous page"
onClick={() => setCurrent(current - 1)}
disabled={!pagination?.prev}
variant="outline"
>
<IconChevronLeft size="18" />
</IconButton>
)}
{pagination?.items.map((page) => {
if (typeof page === "string")
return <span key={page}>...</span>;
return (
<Button
key={page}
onClick={() => setCurrent(page)}
variant={page === current ? "solid" : "outline"}
>
{page}
</Button>
);
})}
{pagination?.next && (
<IconButton
aria-label="next page"
onClick={() => setCurrent(current + 1)}
variant="outline"
>
<IconChevronRight size="18" />
</IconButton>
)}
</HStack>
</Box>
);
};
import React from "react" ;
import { Refine } from "@refinedev/core" ;
import routerProvider from "@refinedev/nextjs-router/pages" ;
import dataProvider from "@refinedev/simple-rest" ;
import type { AppProps } from "next/app" ;
import { RefineThemes , ThemedLayoutV2 , notificationProvider } from "@refinedev/chakra-ui" ;
import { ChakraProvider } from "@chakra-ui/react" ;
function App ( { Component , pageProps } : AppProps) {
return (
< ChakraProvider theme ={ RefineThemes .Blue } >
< Refine
routerProvider ={ routerProvider }
dataProvider ={ dataProvider ( "https://api.fake-rest.refine.dev" ) }
notificationProvider ={ notificationProvider }
resources ={ [
{
name : "products" ,
list : "/products" ,
} ,
] }
>
< ThemedLayoutV2 >
< Component { ... pageProps } />
</ ThemedLayoutV2 >
</ Refine >
</ ChakraProvider >
) ;
}
export default App ;
Dependencies: @refinedev/chakra-ui@^2.26.17,@tabler/icons@^1.119.0,@refinedev/core@^4.45.1,@refinedev/react-router-v6@^4.5.4,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@refinedev/react-hook-form@^4.8.12,@chakra-ui/react@^2.5.1,@refinedev/nextjs-router@latest
Code Files File: /pages/_app.tsx
Content: import React from "react";
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/nextjs-router/pages";
import dataProvider from "@refinedev/simple-rest";
import type { AppProps } from "next/app";
import { RefineThemes, ThemedLayoutV2, notificationProvider } from "@refinedev/chakra-ui";
import { ChakraProvider } from "@chakra-ui/react";
function App({ Component, pageProps }: AppProps) {
return (
<ChakraProvider theme={RefineThemes.Blue}>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
notificationProvider={notificationProvider}
resources={[
{
name: "products",
list: "/products",
},
]}
>
<ThemedLayoutV2>
<Component {...pageProps} />
</ThemedLayoutV2>
</Refine>
</ChakraProvider>
);
}
export default App;
File: /pages/products/index.tsx
Content: import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { GetManyResponse, useMany } from "@refinedev/core";
import {
List,
DateField,
} from "@refinedev/chakra-ui";
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
HStack,
Text,
} from "@chakra-ui/react";
import { Pagination } from "../../components/pagination";
export default function ProductList() {
const columns = React.useMemo(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "name",
header: "Name",
accessorKey: "name",
meta: {
filterOperator: "contains",
},
},
{
id: "price",
header: "Price",
accessorKey: "price",
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
setOptions,
refineCore: {
setCurrent,
pageCount,
current,
tableQueryResult: { data: tableData },
},
} = useTable({
columns,
refineCoreProps: {
initialSorter: [
{
field: "id",
order: "desc",
},
],
},
});
return (
<List>
<TableContainer whiteSpace="pre-line">
<Table variant="simple">
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Text>
{flexRender(
header.column.columnDef
.header,
header.getContext(),
)}
</Text>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Pagination
current={current}
pageCount={pageCount}
setCurrent={setCurrent}
/>
</List>
);
};
import { ThemedLayoutV2 } from "@refinedev/chakra-ui" ;
import { Outlet } from "@remix-run/react" ;
import { LoaderArgs , redirect } from "@remix-run/node" ;
export default function Layout ( ) {
return (
< ThemedLayoutV2 >
< Outlet />
</ ThemedLayoutV2 >
) ;
}
Dependencies: @refinedev/chakra-ui@^2.26.17,@tabler/icons@^1.119.0,@refinedev/core@^4.45.1,@refinedev/react-router-v6@^4.5.4,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@refinedev/react-hook-form@^4.8.12,@chakra-ui/react@^2.5.1,@refinedev/remix-router@latest
Code Files File: /app/root.tsx
Content: import React from "react";
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/remix-router";
import dataProvider from "@refinedev/simple-rest";
import { notificationProvider, RefineThemes } from "@refinedev/chakra-ui";
import { ChakraProvider } from "@chakra-ui/react";
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<ChakraProvider theme={RefineThemes.Blue}>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
notificationProvider={notificationProvider}
resources={[
{
name: "products",
list: "/products",
},
]}
>
<Outlet />
</Refine>
</ChakraProvider>
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
File: /app/routes/_layout.tsx
Content: import { ThemedLayoutV2 } from "@refinedev/chakra-ui";
import { Outlet } from "@remix-run/react";
import { LoaderArgs, redirect } from "@remix-run/node";
/**
* Routes starting with `_layout` will have their children rendered inside the layout.
*/
export default function Layout() {
return (
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
);
}
File: /app/routes/_layout.products._index.tsx
Content: import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { GetManyResponse, useMany } from "@refinedev/core";
import {
List,
DateField,
} from "@refinedev/chakra-ui";
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
HStack,
Text,
} from "@chakra-ui/react";
import { Pagination } from "../components/pagination";
export default function ProductList() {
const columns = React.useMemo(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "name",
header: "Name",
accessorKey: "name",
meta: {
filterOperator: "contains",
},
},
{
id: "price",
header: "Price",
accessorKey: "price",
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
setOptions,
refineCore: {
setCurrent,
pageCount,
current,
tableQueryResult: { data: tableData },
},
} = useTable({
columns,
refineCoreProps: {
initialSorter: [
{
field: "id",
order: "desc",
},
],
},
});
return (
<List>
<TableContainer whiteSpace="pre-line">
<Table variant="simple">
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Text>
{flexRender(
header.column.columnDef
.header,
header.getContext(),
)}
</Text>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Pagination
current={current}
pageCount={pageCount}
setCurrent={setCurrent}
/>
</List>
);
};
<ThemedLayoutV2 />
component consists of a header, sider and a content area. The sider have a navigation menu items for the defined resources of Refine, if an authentication provider is present, it will also have a functional logout buttun. The header contains the app logo and name and also information about the current user if an authentication provider is present.
Additionally, Refine also provides a <Breadcrumb />
component that uses the Chakra UI's component as a base and provide appropriate breadcrumbs for the current route. This component is used in the basic views provided by Refine's Chakra UI package automatically.
Refine's Chakra UI integration offers variety of buttons that are built above the <Button />
component of Chakra UI and includes many logical functionalities such as;
Authorization checks Confirmation dialogs Loading states Invalidation Navigation Form actions Import/Export and more. You can use buttons such as <EditButton />
or <ListButton />
etc. in your views to provide navigation for the related routes or <DeleteButton />
and <SaveButton />
etc. to perform related actions without having to worry about the authorization checks and other logical functionalities.
An example usage of the <EditButton />
component is as follows:
import React from "react" ; import { useTable } from "@refinedev/react-table" ; import { ColumnDef , flexRender } from "@tanstack/react-table" ; import { GetManyResponse , useMany } from "@refinedev/core" ; import { List , EditButton , DateField } from "@refinedev/chakra-ui" ; import { Table , Thead , Tbody , Tr , Th , Td , TableContainer , HStack , Text } from "@chakra-ui/react" ; const columns = [ { id : "id" , header : "ID" , accessorKey : "id" } , { id : "name" , header : "Name" , accessorKey : "name" , meta : { filterOperator : "contains" } } , { id : "price" , header : "Price" , accessorKey : "price" } , { id : "actions" , header : "Actions" , accessorKey : "id" , cell : function render ( { getValue } ) { return ( < EditButton hideText size = " sm " recordItemId = { getValue ( ) as number } /> ) ; } , } , ] ; export const ProductList = ( ) => { const table = useTable < IProduct > ( { columns } ) ; return ( ) ; } ;
The list of provided buttons are:
Many of these buttons are already used in the views provided by Refine's Chakra UI integration. If you're using the basic view elements provided by Refine, you will have the appropriate buttons placed in your application out of the box.
Views Views are designed as wrappers around the content of the pages in the application. They are designed to be used within the layouts and provide basic functionalities such as titles based on the resource, breadcrumbs, related actions and authorization checks. Refine's Chakra UI integration uses components such as <Box />
and <Heading />
to provide these views and provides customization options by passing related props to these components.
The list of provided views are:
list.tsx show.tsx edit.tsx create.tsx index.tsx
import React from "react" ;
import { useTable } from "@refinedev/react-table" ;
import { ColumnDef , flexRender } from "@tanstack/react-table" ;
import { GetManyResponse , useMany } from "@refinedev/core" ;
import {
List ,
ShowButton ,
EditButton ,
DeleteButton ,
DateField ,
} from "@refinedev/chakra-ui" ;
import {
Table ,
Thead ,
Tbody ,
Tr ,
Th ,
Td ,
TableContainer ,
HStack ,
Text ,
} from "@chakra-ui/react" ;
import { Pagination } from "../../components/pagination" ;
export const ProductList = ( ) => {
const columns = React .useMemo (
( ) => [
{
id : "id" ,
header : "ID" ,
accessorKey : "id" ,
} ,
{
id : "name" ,
header : "Name" ,
accessorKey : "name" ,
meta : {
filterOperator : "contains" ,
} ,
} ,
{
id : "price" ,
header : "Price" ,
accessorKey : "price" ,
} ,
{
id : "actions" ,
header : "Actions" ,
accessorKey : "id" ,
enableColumnFilter : false ,
enableSorting : false ,
cell : function render ( { getValue } ) {
return (
< HStack >
< ShowButton
hideText
size ="sm"
recordItemId ={ getValue ( ) as number}
/>
< EditButton
hideText
size ="sm"
recordItemId ={ getValue ( ) as number}
/>
< DeleteButton
hideText
size ="sm"
recordItemId ={ getValue ( ) as number}
/>
</ HStack >
) ;
} ,
} ,
] ,
[ ] ,
) ;
const {
getHeaderGroups ,
getRowModel ,
setOptions ,
refineCore : {
setCurrent ,
pageCount ,
current ,
tableQueryResult : { data : tableData } ,
} ,
} = useTable ( {
columns ,
refineCoreProps : {
initialSorter : [
{
field : "id" ,
order : "desc" ,
} ,
] ,
} ,
} ) ;
return (
< List >
< TableContainer whiteSpace ="pre-line" >
< Table variant ="simple" >
< Thead >
{ getHeaderGroups ( ) .map ( ( headerGroup ) => (
< Tr key ={ headerGroup .id } >
{ headerGroup .headers .map ( ( header ) => (
< Th key ={ header .id } >
< Text >
{ flexRender (
header .column .columnDef
.header ,
header .getContext ( ) ,
) }
</ Text >
</ Th >
) ) }
</ Tr >
) ) }
</ Thead >
< Tbody >
{ getRowModel ( ) .rows .map ( ( row ) => (
< Tr key ={ row .id } >
{ row .getVisibleCells ( ) .map ( ( cell ) => (
< Td key ={ cell .id } >
{ flexRender (
cell .column .columnDef .cell ,
cell .getContext ( ) ,
) }
</ Td >
) ) }
</ Tr >
) ) }
</ Tbody >
</ Table >
</ TableContainer >
< Pagination
current ={ current }
pageCount ={ pageCount }
setCurrent ={ setCurrent }
/>
</ List >
) ;
} ;
Dependencies: @refinedev/chakra-ui@^2.26.17,@tabler/icons@^1.119.0,@refinedev/core@^4.45.1,@refinedev/react-router-v6@^4.5.4,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@refinedev/react-hook-form@^4.8.12,@chakra-ui/react@^2.5.1,react-dom@^18.0.0,react-router@latest,react-router-dom@^6.8.1,react-hook-form@^7.30.0
Code Files File: /App.tsx
Content: import { Refine } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider, { NavigateToResource } from "@refinedev/react-router-v6";
import { BrowserRouter, Route, Routes, Outlet } from "react-router-dom";
import {
ThemedLayoutV2,
ErrorComponent,
RefineThemes,
notificationProvider,
} from "@refinedev/chakra-ui";
import { ChakraProvider } from "@chakra-ui/react";
import { ProductList, ProductShow, ProductEdit, ProductCreate } from "./pages/products";
export default function App() {
return (
<BrowserRouter>
<ChakraProvider theme={RefineThemes.Blue}>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider(
"https://api.fake-rest.refine.dev",
)}
notificationProvider={notificationProvider}
resources={[
{
name: "products",
list: "/products",
show: "/products/:id",
edit: "/products/:id/edit",
create: "/products/create",
meta: {
canDelete: true,
},
},
]}
options={{
syncWithLocation: true,
}}
>
<Routes>
<Route
element={
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
}
>
<Route index element={<NavigateToResource resource="products" />} />
<Route path="/products" element={<Outlet />}>
<Route index element={<ProductList />} />
<Route path="create" element={<ProductCreate />} />
<Route path=":id" element={<ProductShow />} />
<Route path=":id/edit" element={<ProductEdit />} />
</Route>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</Refine>
</ChakraProvider>
</BrowserRouter>
);
};
File: /pages/products/index.tsx
Content: export * from "./list";
export * from "./show";
export * from "./edit";
export * from "./create";
File: /pages/products/list.tsx
Content: import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { GetManyResponse, useMany } from "@refinedev/core";
import {
List,
ShowButton,
EditButton,
DeleteButton,
DateField,
} from "@refinedev/chakra-ui";
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
HStack,
Text,
} from "@chakra-ui/react";
import { Pagination } from "../../components/pagination";
export const ProductList = () => {
const columns = React.useMemo(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "name",
header: "Name",
accessorKey: "name",
meta: {
filterOperator: "contains",
},
},
{
id: "price",
header: "Price",
accessorKey: "price",
},
{
id: "actions",
header: "Actions",
accessorKey: "id",
enableColumnFilter: false,
enableSorting: false,
cell: function render({ getValue }) {
return (
<HStack>
<ShowButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<EditButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<DeleteButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
</HStack>
);
},
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
setOptions,
refineCore: {
setCurrent,
pageCount,
current,
tableQueryResult: { data: tableData },
},
} = useTable({
columns,
refineCoreProps: {
initialSorter: [
{
field: "id",
order: "desc",
},
],
},
});
return (
<List>
<TableContainer whiteSpace="pre-line">
<Table variant="simple">
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Text>
{flexRender(
header.column.columnDef
.header,
header.getContext(),
)}
</Text>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Pagination
current={current}
pageCount={pageCount}
setCurrent={setCurrent}
/>
</List>
);
};
File: /pages/products/show.tsx
Content: import { useShow } from "@refinedev/core";
import { Show, TextField, NumberField, MarkdownField } from "@refinedev/chakra-ui";
import { Heading } from "@chakra-ui/react";
export const ProductShow = () => {
const { queryResult } = useShow();
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Heading as="h5" size="sm">
Id
</Heading>
<TextField value={record?.id} />
<Heading as="h5" size="sm" mt={4}>
Name
</Heading>
<TextField value={record?.name} />
<Heading as="h5" size="sm" mt={4}>
Material
</Heading>
<TextField value={record?.material} />
<Heading as="h5" size="sm" mt={4}>
Description
</Heading>
<MarkdownField value={record?.description} />
<Heading as="h5" size="sm" mt={4}>
Price
</Heading>
<NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} />
</Show>
);
};
File: /pages/products/edit.tsx
Content: import { Edit } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
export const ProductEdit = () => {
const {
refineCore: { formLoading, queryResult, autoSaveProps },
saveButtonProps,
register,
formState: { errors },
setValue,
} = useForm({
refineCoreProps: {
autoSave: {
enabled: true,
},
},
});
return (
<Edit
isLoading={formLoading}
saveButtonProps={saveButtonProps}
autoSaveProps={autoSaveProps}
>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Edit>
);
};
File: /pages/products/create.tsx
Content: import { Create } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
export const ProductCreate = () => {
const {
refineCore: { formLoading },
saveButtonProps,
register,
formState: { errors },
} = useForm<IPost>();
return (
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Create>
);
};
File: /components/pagination/index.tsx
Content:
import React from "react";
import { HStack, Button, Box } from "@chakra-ui/react";
import { IconChevronRight, IconChevronLeft } from "@tabler/icons";
import { usePagination } from "@refinedev/chakra-ui";
import { IconButton } from "@chakra-ui/react";
type PaginationProps = {
current: number;
pageCount: number;
setCurrent: (page: number) => void;
};
export const Pagination: React.FC<PaginationProps> = ({
current,
pageCount,
setCurrent,
}) => {
const pagination = usePagination({
current,
pageCount,
});
return (
<Box display="flex" justifyContent="flex-end">
<HStack my="3" spacing="1">
{pagination?.prev && (
<IconButton
aria-label="previous page"
onClick={() => setCurrent(current - 1)}
disabled={!pagination?.prev}
variant="outline"
>
<IconChevronLeft size="18" />
</IconButton>
)}
{pagination?.items.map((page) => {
if (typeof page === "string")
return <span key={page}>...</span>;
return (
<Button
key={page}
onClick={() => setCurrent(page)}
variant={page === current ? "solid" : "outline"}
>
{page}
</Button>
);
})}
{pagination?.next && (
<IconButton
aria-label="next page"
onClick={() => setCurrent(current + 1)}
variant="outline"
>
<IconChevronRight size="18" />
</IconButton>
)}
</HStack>
</Box>
);
};
Fields Refine's Chakra UI also provides field components to render values with appropriate design and format of Chakra UI. These components are built on top of respective Chakra UI components and also provide logic for formatting of the values. While these components might not always be suitable for your use case, they can be combined or extended to provide the desired functionality.
The list of provided field components are:
import { useShow } from "@refinedev/core" ; import { Show , TextField , NumberField , MarkdownField , } from "@refinedev/chakra-ui" ; import { Heading } from "@chakra-ui/react" ; export const ProductShow = ( ) => { const { queryResult } = useShow ( ) ; const { data , isLoading } = queryResult ; const record = data ?. data ; return ( < Show isLoading = { isLoading } > < Heading as = " h5 " size = " sm " > Id </ Heading > < TextField value = { record ?. id } /> < Heading as = " h5 " size = " sm " mt = { 4 } > Name </ Heading > < TextField value = { record ?. name } /> < Heading as = " h5 " size = " sm " mt = { 4 } > Material </ Heading > < TextField value = { record ?. material } /> < Heading as = " h5 " size = " sm " mt = { 4 } > Description </ Heading > < MarkdownField value = { record ?. description } /> < Heading as = " h5 " size = " sm " mt = { 4 } > Price </ Heading > < NumberField value = { record ?. price } options = { { style : "currency" , currency : "USD" } } /> </ Show > ) ; } ;
Auth Pages Auth pages are designed to be used as the pages of the authentication flow of the application. They offer an out of the box solution for the login, register, forgot password and reset password pages by leveraging the authentication hooks of Refine. Auth page components are built on top of basic Chakra UI components such as <Input />
and <Card />
etc.
The list of types of auth pages that are available in the UI integrations are:
<AuthPage type="login" />
<AuthPage type="register" />
<AuthPage type="forgot-password" />
<AuthPage type="reset-password" />
An example usage of the <AuthPage />
component is as follows:
login.tsx register.tsx forgot-password.tsx reset-password.tsx
import { AuthPage } from "@refinedev/chakra-ui" ;
export const LoginPage = ( ) => {
return (
< AuthPage
type ="login"
formProps ={ {
defaultValues : {
email : "demo@refine.dev" ,
password : "demodemo" ,
} ,
} }
/>
) ;
} ;
Dependencies: @refinedev/chakra-ui@^2.26.17,@tabler/icons@^1.119.0,@refinedev/core@^4.45.1,@refinedev/react-router-v6@^4.5.4,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@refinedev/react-hook-form@^4.8.12,@chakra-ui/react@^2.5.1,react-dom@^18.0.0,react-router@latest,react-router-dom@^6.8.1,react-hook-form@^7.30.0
Code Files File: /App.tsx
Content: import React from "react";
import { Refine, Authenticated } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider, { NavigateToResource } from "@refinedev/react-router-v6";
import { BrowserRouter, Route, Routes, Outlet, Navigate } from "react-router-dom";
import { ErrorComponent, RefineThemes, ThemedLayoutV2, notificationProvider, AuthPage } from "@refinedev/chakra-ui";
import { ChakraProvider } from "@chakra-ui/react";
import authProvider from "./auth-provider";
import { ProductList } from "./pages/products";
import { LoginPage } from "./pages/login";
import { RegisterPage } from "./pages/register";
import { ForgotPasswordPage } from "./pages/forgot-password";
import { ResetPasswordPage } from "./pages/reset-password";
export default function App() {
return (
<BrowserRouter>
<ChakraProvider theme={RefineThemes.Blue}>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
authProvider={authProvider}
notificationProvider={notificationProvider}
resources={[
{
name: "products",
list: "/products",
}
]}
options={{ syncWithLocation: true }}
>
<Routes>
<Route element={<Authenticated fallback={<Navigate to="/login" />}><Outlet /></Authenticated>}>
<Route
element={
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
}
>
<Route path="/products" element={<ProductList />} />
<Route path="*" element={<ErrorComponent />} />
</Route>
</Route>
<Route element={<Authenticated fallback={<Outlet />}><NavigateToResource resource="products" /></Authenticated>}>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
<Route path="/reset-password" element={<ResetPasswordPage />} />
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</Refine>
</ChakraProvider>
</BrowserRouter>
);
};
File: /pages/products.tsx
Content: import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { GetManyResponse, useMany } from "@refinedev/core";
import {
List,
} from "@refinedev/chakra-ui";
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
HStack,
Text,
} from "@chakra-ui/react";
export const ProductList = () => {
const columns = React.useMemo(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "name",
header: "Name",
accessorKey: "name",
meta: {
filterOperator: "contains",
},
},
{
id: "price",
header: "Price",
accessorKey: "price",
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
setOptions,
refineCore: {
setCurrent,
pageCount,
current,
tableQueryResult: { data: tableData },
},
} = useTable({
columns,
refineCoreProps: {
initialSorter: [
{
field: "id",
order: "desc",
},
],
},
});
return (
<List>
<TableContainer whiteSpace="pre-line">
<Table variant="simple">
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Text>
{flexRender(
header.column.columnDef
.header,
header.getContext(),
)}
</Text>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</List>
);
};
File: /pages/login.tsx
Content: import { AuthPage } from "@refinedev/chakra-ui";
export const LoginPage = () => {
return (
<AuthPage
type="login"
formProps={{
defaultValues: {
email: "demo@refine.dev",
password: "demodemo",
},
}}
/>
);
};
File: /pages/register.tsx
Content: import { AuthPage } from "@refinedev/chakra-ui";
export const RegisterPage = () => {
return <AuthPage type="register" />;
};
File: /pages/forgot-password.tsx
Content: import { AuthPage } from "@refinedev/chakra-ui";
export const ForgotPasswordPage = () => {
return <AuthPage type="forgotPassword" />;
};
File: /pages/reset-password.tsx
Content: import { AuthPage } from "@refinedev/chakra-ui";
export const ResetPasswordPage = () => {
return <AuthPage type="resetPassword" />;
};
File: /auth-provider.tsx
Content: const authProvider = {
login: async ({ username, password }) => {
(window as any).authenticated = true;
return { success: true };
},
check: async () => {
return { authenticated: Boolean((window as any).authenticated) };
},
logout: async () => {
(window as any).authenticated = false;
return { success: true };
},
register: async () => {
return { success: true };
},
forgotPassword: async () => {
return { success: true };
},
resetPassword: async () => {
return { success: true };
},
getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"})
};
export default authProvider;
Error Components Refine's Chakra UI integration also provides an <ErrorComponent />
component that you can use to render a 404 page in your app. While these components does not offer much functionality, they are provided as an easy way to render an error page with a consistent design language.
An example usage of the <ErrorComponent />
component is as follows:
import { ErrorComponent } from "@refinedev/chakra-ui" ; const NotFoundPage = ( ) => { return < ErrorComponent /> ; } ;
Theming Since Refine offers application level components such as layout, sidebar and header and page level components for each action, it is important to have it working with the styling of Chakra UI. All components and providers exported from the @refinedev/chakra-ui
package will use the current theme of Chakra UI without any additional configuration.
Additionally, Refine also provides a set of carefully crafted themes for Chakra UI which outputs a nice UI with Refine's components with light and dark theme support. These themes are exported as RefineThemes
object from the @refinedev/chakra-ui
package and can be used in <ChakraProvider />
component of Chakra UI.
import { RefineThemes } from "@refinedev/chakra-ui" ;
import { ChakraProvider } from "@chakra-ui/react" ;
export const ThemeProvider = ( { children } ) => (
< ChakraProvider theme ={ RefineThemes .Magenta } >
{ children }
</ ChakraProvider >
) ;
Dependencies: @refinedev/chakra-ui@^2.26.17,@tabler/icons@^1.119.0,@refinedev/core@^4.45.1,@refinedev/react-router-v6@^4.5.4,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@refinedev/react-hook-form@^4.8.12,@chakra-ui/react@^2.5.1,react-dom@^18.0.0,react-router@latest,react-router-dom@^6.8.1,react-hook-form@^7.30.0
Code Files File: /App.tsx
Content: import { Refine, Authenticated } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider, { NavigateToResource } from "@refinedev/react-router-v6";
import { BrowserRouter, Route, Routes, Outlet, Navigate } from "react-router-dom";
import {
ThemedLayoutV2,
ErrorComponent,
notificationProvider,
AuthPage,
} from "@refinedev/chakra-ui";
import { ThemeProvider } from "./theme-provider";
import authProvider from "./auth-provider";
import { ProductList, ProductShow, ProductEdit, ProductCreate } from "./pages/products";
export default function App() {
return (
<BrowserRouter>
<ThemeProvider>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider(
"https://api.fake-rest.refine.dev",
)}
notificationProvider={notificationProvider}
authProvider={authProvider}
resources={[
{
name: "products",
list: "/products",
show: "/products/:id",
edit: "/products/:id/edit",
create: "/products/create",
meta: {
canDelete: true,
},
},
]}
options={{
syncWithLocation: true,
}}
>
<Routes>
<Route element={<Authenticated fallback={<Navigate to="/login" />}><Outlet /></Authenticated>}>
<Route
element={
<ThemedLayoutV2>
<Outlet />
</ThemedLayoutV2>
}
>
<Route index element={<NavigateToResource resource="products" />} />
<Route path="/products" element={<Outlet />}>
<Route index element={<ProductList />} />
<Route path="create" element={<ProductCreate />} />
<Route path=":id" element={<ProductShow />} />
<Route path=":id/edit" element={<ProductEdit />} />
</Route>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Route>
<Route element={<Authenticated fallback={<Outlet />}><NavigateToResource resource="products" /></Authenticated>}>
<Route
path="/login"
element={(
<AuthPage
type="login"
formProps={{
defaultValues: {
email: "demo@refine.dev",
password: "demodemo",
},
}}
/>
)}
/>
<Route path="/register" element={<AuthPage type="register" />} />
<Route path="/forgot-password" element={<AuthPage type="forgotPassword" />} />
<Route path="/reset-password" element={<AuthPage type="resetPassword" />} />
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</Refine>
</ThemeProvider>
</BrowserRouter>
);
};
File: /theme-provider.tsx
Content: import { RefineThemes } from "@refinedev/chakra-ui";
import { ChakraProvider } from "@chakra-ui/react";
export const ThemeProvider = ({ children }) => (
// Available themes: Blue, Purple, Magenta, Red, Orange, Yellow, Green
// Change the line below to change the theme
<ChakraProvider theme={RefineThemes.Magenta}>
{children}
</ChakraProvider>
);
File: /pages/products/index.tsx
Content: export * from "./list";
export * from "./show";
export * from "./edit";
export * from "./create";
File: /pages/products/list.tsx
Content: import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { GetManyResponse, useMany } from "@refinedev/core";
import {
List,
ShowButton,
EditButton,
DeleteButton,
DateField,
} from "@refinedev/chakra-ui";
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
HStack,
Text,
} from "@chakra-ui/react";
import { Pagination } from "../../components/pagination";
export const ProductList = () => {
const columns = React.useMemo(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "name",
header: "Name",
accessorKey: "name",
meta: {
filterOperator: "contains",
},
},
{
id: "price",
header: "Price",
accessorKey: "price",
},
{
id: "actions",
header: "Actions",
accessorKey: "id",
enableColumnFilter: false,
enableSorting: false,
cell: function render({ getValue }) {
return (
<HStack>
<ShowButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<EditButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
<DeleteButton
hideText
size="sm"
recordItemId={getValue() as number}
/>
</HStack>
);
},
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
setOptions,
refineCore: {
setCurrent,
pageCount,
current,
tableQueryResult: { data: tableData },
},
} = useTable({
columns,
refineCoreProps: {
initialSorter: [
{
field: "id",
order: "desc",
},
],
},
});
return (
<List>
<TableContainer whiteSpace="pre-line">
<Table variant="simple">
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Text>
{flexRender(
header.column.columnDef
.header,
header.getContext(),
)}
</Text>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Pagination
current={current}
pageCount={pageCount}
setCurrent={setCurrent}
/>
</List>
);
};
File: /pages/products/show.tsx
Content: import { useShow } from "@refinedev/core";
import { Show, TextField, NumberField, MarkdownField } from "@refinedev/chakra-ui";
import { Heading } from "@chakra-ui/react";
export const ProductShow = () => {
const { queryResult } = useShow();
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Heading as="h5" size="sm">
Id
</Heading>
<TextField value={record?.id} />
<Heading as="h5" size="sm" mt={4}>
Name
</Heading>
<TextField value={record?.name} />
<Heading as="h5" size="sm" mt={4}>
Material
</Heading>
<TextField value={record?.material} />
<Heading as="h5" size="sm" mt={4}>
Description
</Heading>
<MarkdownField value={record?.description} />
<Heading as="h5" size="sm" mt={4}>
Price
</Heading>
<NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} />
</Show>
);
};
File: /pages/products/edit.tsx
Content: import { Edit } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
export const ProductEdit = () => {
const {
refineCore: { formLoading, queryResult, autoSaveProps },
saveButtonProps,
register,
formState: { errors },
setValue,
} = useForm({
refineCoreProps: {
autoSave: {
enabled: true,
},
},
});
return (
<Edit
isLoading={formLoading}
saveButtonProps={saveButtonProps}
autoSaveProps={autoSaveProps}
>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Edit>
);
};
File: /pages/products/create.tsx
Content: import { Create } from "@refinedev/chakra-ui";
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
Textarea,
} from "@chakra-ui/react";
import { useForm } from "@refinedev/react-hook-form";
export const ProductCreate = () => {
const {
refineCore: { formLoading },
saveButtonProps,
register,
formState: { errors },
} = useForm<IPost>();
return (
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
<FormControl mb="3" isInvalid={!!errors?.name}>
<FormLabel>Name</FormLabel>
<Input
id="name"
type="text"
{...register("name", { required: "Name is required" })}
/>
<FormErrorMessage>
{`${errors.name?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.material}>
<FormLabel>Material</FormLabel>
<Input
id="material"
type="text"
{...register("material", { required: "Material is required" })}
/>
<FormErrorMessage>
{`${errors.material?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.description}>
<FormLabel>Description</FormLabel>
<Textarea
id="description"
{...register("description", {
required: "Description is required",
})}
/>
<FormErrorMessage>
{`${errors.description?.message}`}
</FormErrorMessage>
</FormControl>
<FormControl mb="3" isInvalid={!!errors?.price}>
<FormLabel>Price</FormLabel>
<Input
id="price"
type="number"
{...register("price", { required: "Price is required" })}
/>
<FormErrorMessage>
{`${errors.price?.message}`}
</FormErrorMessage>
</FormControl>
</Create>
);
};
File: /components/pagination/index.tsx
Content:
import React from "react";
import { HStack, Button, Box } from "@chakra-ui/react";
import { IconChevronRight, IconChevronLeft } from "@tabler/icons";
import { usePagination } from "@refinedev/chakra-ui";
import { IconButton } from "@chakra-ui/react";
type PaginationProps = {
current: number;
pageCount: number;
setCurrent: (page: number) => void;
};
export const Pagination: React.FC<PaginationProps> = ({
current,
pageCount,
setCurrent,
}) => {
const pagination = usePagination({
current,
pageCount,
});
return (
<Box display="flex" justifyContent="flex-end">
<HStack my="3" spacing="1">
{pagination?.prev && (
<IconButton
aria-label="previous page"
onClick={() => setCurrent(current - 1)}
disabled={!pagination?.prev}
variant="outline"
>
<IconChevronLeft size="18" />
</IconButton>
)}
{pagination?.items.map((page) => {
if (typeof page === "string")
return <span key={page}>...</span>;
return (
<Button
key={page}
onClick={() => setCurrent(page)}
variant={page === current ? "solid" : "outline"}
>
{page}
</Button>
);
})}
{pagination?.next && (
<IconButton
aria-label="next page"
onClick={() => setCurrent(current + 1)}
variant="outline"
>
<IconChevronRight size="18" />
</IconButton>
)}
</HStack>
</Box>
);
};
File: /auth-provider.tsx
Content: const authProvider = {
login: async ({ username, password }) => {
(window as any).authenticated = true;
return { success: true };
},
check: async () => {
// auto login at first time
if (typeof (window as any).authenticated === "undefined") {
(window as any).authenticated = true;
}
return { authenticated: Boolean((window as any).authenticated) };
},
logout: async () => {
(window as any).authenticated = false;
return { success: true };
},
register: async () => {
return { success: true };
},
forgotPassword: async () => {
return { success: true };
},
resetPassword: async () => {
return { success: true };
},
getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"})
};
export default authProvider;
To learn more about the theme configuration of Chakra UI, please refer to the official documentation .
Inferencer You can automatically generate views for your resources using @refinedev/inferencer
. Inferencer exports the ChakraListInferencer
, ChakraShowInferencer
, ChakraEditInferencer
, ChakraCreateInferencer
components and finally the ChakraInferencer
component, which combines all in one place.
To learn more about Inferencer, please refer to the Chakra UI Inferencer docs.