Skip to main content
Version: 4.xx.xx
Source Code

useEditableTable

useEditeableTable allows you to implement the edit feature on the <Table> with ease and returns properties that can be used on Ant Design's <Table> and <Form> components.

useEditeableTable hook is extended from the useTable hook from the @refinedev/antd package. This means that you can use all the features of useTable hook.

Usage

Here is an example of how to use useEditableTable hook. We will explain the details of this example and hooks usage in the following sections.

localhost:3000/posts
import {
List,
SaveButton,
EditButton,
TextField,
useEditableTable,
} from "@refinedev/antd";
import { Table, Form, Space, Button, Input } from "antd";

interface IPost {
id: number;
title: string;
}

const PostList: React.FC = () => {
const {
tableProps,
formProps,
isEditing,
setId: setEditId,
saveButtonProps,
cancelButtonProps,
editButtonProps,
} = useEditableTable<IPost>();

return (
<List>
<Form {...formProps}>
<Table
{...tableProps}
rowKey="id"
onRow={(record) => ({
// eslint-disable-next-line
onClick: (event: any) => {
if (event.target.nodeName === "TD") {
setEditId && setEditId(record.id);
}
},
})}
>
<Table.Column dataIndex="id" title="ID" />
<Table.Column<IPost>
dataIndex="title"
title="Title"
render={(value, record) => {
if (isEditing(record.id)) {
return (
<Form.Item name="title" style={{ margin: 0 }}>
<Input />
</Form.Item>
);
}
return <TextField value={value} />;
}}
/>
<Table.Column<IPost>
title="Actions"
dataIndex="actions"
render={(_, record) => {
if (isEditing(record.id)) {
return (
<Space>
<SaveButton {...saveButtonProps} hideText size="small" />
<Button {...cancelButtonProps} size="small">
Cancel
</Button>
</Space>
);
}
return (
<EditButton
{...editButtonProps(record.id)}
hideText
size="small"
/>
);
}}
/>
</Table>
</Form>
</List>
);
};

Editing with buttons

Let's say that we want to make the Post data where we show the id and title values a listing page:

This time, to add the edit feature, we have to cover the <Table> component with a <Form> component and pass the properties coming from useEditableTable to the corresponding components:

/pages/posts/list.tsx
import { List, useEditableTable, TextField } from "@refinedev/antd";
import { Table, Form } from "antd";

export const PostList: React.FC = () => {
const { tableProps, formProps } = useEditableTable<IPost>();

return (
<List>
<Form {...formProps}>
<Table {...tableProps} rowKey="id">
<Table.Column dataIndex="id" title="ID" />
<Table.Column dataIndex="title" title="Title" />
</Table>
</Form>
</List>
);
};

interface IPost {
id: number;
title: string;
}

Now lets add a column for edit buttons:

/pages/posts/list.tsx
import {
List,
SaveButton,
EditButton,
useEditableTable,
} from "@refinedev/antd";
import {
Table,
Form,
Space,
Button,
} from "antd";

export const PostList: React.FC = () => {
const {
tableProps,
formProps,
isEditing,
saveButtonProps,
cancelButtonProps,
editButtonProps,
} = useEditableTable<IPost>();

return (
<List>
<Form {...formProps}>
<Table {...tableProps} rowKey="id">
<Table.Column key="id" dataIndex="id" title="ID" />
<Table.Column key="title" dataIndex="title" title="Title" />
<Table.Column<IPost>
title="Actions"
dataIndex="actions"
key="actions"
render={(_text, record) => {
if (isEditing(record.id)) {
return (
<Space>
<SaveButton {...saveButtonProps} size="small" />
<Button {...cancelButtonProps} size="small">
Cancel
</Button>
</Space>
);
}
return (
<Space>
<EditButton {...editButtonProps(record.id)} size="small" />
</Space>
);
}}
/>
</Table>
</Form>
</List>
);
};

isEditing function that returns from useEditableTable lets us check whether a line is currently in edit mode or not.

For now, our post is not editable yet. If a post is being edited, we must show editable columns inside a <Form.Item> using conditional rendering:

/pages/posts/list.tsx
import {
List,
SaveButton,
EditButton,
TextField,
useEditableTable,
} from "@refinedev/antd";
import {
Table,
Form,
Space,
Button,
Input,
} from "antd";

export const PostList: React.FC = () => {
const {
tableProps,
formProps,
isEditing,
saveButtonProps,
cancelButtonProps,
editButtonProps,
} = useEditableTable<IPost>();

return (
<List>
<Form {...formProps}>
<Table {...tableProps} rowKey="id">
<Table.Column key="id" dataIndex="id" title="ID" />
<Table.Column<IPost>
key="title"
dataIndex="title"
title="Title"
render={(value, record) => {
if (isEditing(record.id)) {
return (
<Form.Item name="title" style={{ margin: 0 }}>
<Input />
</Form.Item>
);
}
return <TextField value={value} />;
}}
/>
<Table.Column<IPost>
title="Actions"
dataIndex="actions"
key="actions"
render={(_text, record) => {
if (isEditing(record.id)) {
return (
<Space>
<SaveButton {...saveButtonProps} size="small" />
<Button {...cancelButtonProps} size="small">
Cancel
</Button>
</Space>
);
}
return (
<Space>
<EditButton {...editButtonProps(record.id)} size="small" />
</Space>
);
}}
/>
</Table>
</Form>
</List>
);
};

With this, when a user clicks on the edit button, isEditing(lineId) will turn true for the relevant line. This will also cause <TextInput> to show up on the line that's being edited. When the editing is finished, a new value can be saved by clicking <SaveButton>.

Implementation Tips:

By giving the <Table.Column> component a unique render property, you can render the value in that column however you want.

For more information, refer to the <Table.Column> documentation

Editing by clicking to row

A line with the id value can be put to edit mode programmatically by using the setId function that returns from useEditableTable.

The onRow property of the <Table> component can be used to put a line to editing mode when it's clicked on. The function given to the onRow property is called every time one of these lines is clicked on, with the information of which line was clicked on.

We can use setId to put a line to edit mode whenever it's clicked on.

/pages/posts/list.tsx
import { List, TextField, useEditableTable } from "@refinedev/antd";
import { Table, Form, Input } from "antd";

export const PostList: React.FC = () => {
const { tableProps, formProps, isEditing, setId } = useEditableTable<IPost>();

return (
<List>
<Form {...formProps}>
<Table
{...tableProps}
key="id"
onRow={(record) => ({
onClick: (event: any) => {
if (event.target.nodeName === "TD") {
setId && setId(record.id);
}
},
})}
>
<Table.Column key="id" dataIndex="id" title="ID" />
<Table.Column<IPost>
key="title"
dataIndex="title"
title="Title"
render={(value, data: any) => {
if (isEditing(data.id)) {
return (
<Form.Item name="title" style={{ margin: 0 }}>
<Input />
</Form.Item>
);
}
return <TextField value={value} />;
}}
/>
</Table>
</Form>
</List>
);
};

Properties

All useForm and useTable properties are available in useEditableTable. You can read the documentation of useForm and useTable for more information.

autoSubmitClose

autoSubmitClose makes the table's row close after a successful submit. It is true by default.

For this effect, useEditableTable automatically calls the setId function with undefined after successful submit.

const editableTable = useEditableTable({
autoSubmitClose: false,
});

Return Values

All useForm and useTable return values are available in useEditableTable. You can read the documentation of useForm and useTable for more information.

cancelButtonProps

cancelButtonProps returns the props for needed by the <EditButton>.

By default, the onClick function is overridden by useEditableTable. Which will call useForm's setId function with undefined when called.

cancelButtonProps: () => ButtonProps;

editButtonProps

editButtonProps takes id as a parameter and returns the props needed by the <EditButton>.

By default, the onClick function is overridden by useEditableTable. Which will call useForm's setId function with the given id when called.

editButtonProps: (id: BaseKey) => ButtonProps;

It also returns a function that takes an id as a parameter and returns the props for the edit button.

isEditing

isEditing: (id: BaseKey) => boolean;

Takes a id as a parameter and returns true if the given BaseKey is equal to the selected useForm's id.

API

Properties

PropertyTypeDescriptionDefault
resource

string

Resource name for API data interactions

Resource name that it reads from route Resource name that it reads from route

meta

MetaQuery

Metadata query for dataProvider

metaData

metaData is deprecated with refine@4, refine will pass meta instead, however, we still support metaData for backward compatibility. metaData is deprecated with refine@4, refine will pass meta instead, however, we still support metaData for backward compatibility.

MetaQuery

Metadata query for dataProvider

liveMode

Whether to update data automatically ("auto") or not ("manual") if a related live event is received. The "off" value is used to avoid creating a subscription.

"off"

liveParams

Params to pass to liveProvider's subscribe method if liveMode is enabled.

undefined

dataProviderName

string

If there is more than one dataProvider, you should use the dataProviderName that you will use.

onLiveEvent

Callback to handle all related live events of this hook.

undefined

queryOptions

(UseQueryOptions<GetListResponse<TQueryFnData>, TError, GetListResponse<TData>, QueryKey> & UseQueryOptions<...>)

react-query's useQuery options react-query's useQuery options of useOne hook used while in edit mode.

overtimeOptions

UseLoadingOvertimeCoreOptions

syncWithLocation

boolean

Sortings, filters, page index and records shown per page are tracked by browser history

Value set in Refine. If a custom resource is given, it will be false

onSearch

((data: TSearchVariables) => CrudFilters

Promise<CrudFilters>)

initialCurrent

initialCurrent property is deprecated. Use pagination.current instead.

number

Initial page index

1

initialPageSize

initialPageSize property is deprecated. Use pagination.pageSize instead.

number

Initial number of items per page

10

hasPagination

hasPagination property is deprecated. Use pagination.mode instead.

boolean

Whether to use server side pagination or not.

true

pagination

Pagination

Configuration for pagination

initialSorter

initialSorter property is deprecated. Use sorters.initial instead.

CrudSort[]

Initial sorter state

permanentSorter

permanentSorter property is deprecated. Use sorters.permanent instead.

CrudSort[]

Default and unchangeable sorter state

[]

initialFilter

initialFilter property is deprecated. Use filters.initial instead.

CrudFilter[]

Initial filter state

permanentFilter

permanentFilter property is deprecated. Use filters.permanent instead.

CrudFilter[]

Default and unchangeable filter state

[]

defaultSetFilterBehavior

defaultSetFilterBehavior property is deprecated. Use filters.defaultBehavior instead.

"merge" | "replace"

Default behavior of the setFilters function

"merge"

filters

{ initial?: CrudFilter[]; permanent?: CrudFilter[]; defaultBehavior?: SetFilterBehavior

undefined; mode?: "off"

"server"

undefined; }

undefined

Filter configs

sorters

{ initial?: CrudSort[]; permanent?: CrudSort[]; mode?: "off"

"server"

undefined; }

undefined

Sort configs

id

Record id for fetching

Id that it reads from the URL

redirect

"show" | "edit" | "list" | "create" | false

Page to redirect after a succesfull mutation

"list"

queryMeta

MetaQuery

Metadata to pass for the useOne query

mutationMeta

MetaQuery

Metadata to pass for the mutation (useCreate for create and clone actions, useUpdate for edit action)

mutationMode

"pessimistic" | "optimistic" | "undoable"

Determines when mutations are executed

"pessimistic"*

onMutationSuccess

((data: UpdateResponse<TQueryFnData>

CreateResponse<TQueryFnData>, variables: TVariables, context: any, isAutoSave?: boolean) => void)

Called when a mutation is successful

onMutationError

((error: TError, variables: TVariables, context: any, isAutoSave?: boolean) => void)

Called when a mutation encounters an error

undoableTimeout

number

Duration to wait before executing mutations when mutationMode = "undoable"

5000*

invalidates

all, resourceAll, list, many, detail, false

You can use it to manage the invalidations that will occur at the end of the mutation.

["list", "many", "detail"]

createMutationOptions

Omit<UseMutationOptions<CreateResponse<TQueryFnData>, TError, UseCreateParams<TQueryFnData, TError, TVariables>, unknown>, "mutationFn"

... 1 more ...

"onSuccess">

react-query's useMutation options of useCreate hook used while submitting in create and clone modes.

updateMutationOptions

Omit<UseMutationOptions<UpdateResponse<TQueryFnData>, TError, UpdateParams<TQueryFnData, TError, TVariables>, PrevContext<...>>, "mutationFn"

... 3 more ...

"onSettled">

react-query's useMutation options of useUpdate hook used while submitting in edit mode.

optimisticUpdateMap

OptimisticUpdateMapType<TQueryFnData, TVariables>

If you customize the optimisticUpdateMap option, you can use it to manage the invalidations that will occur at the end of the mutation.

{ list: true, many: true, detail: true, }

successNotification

false

OpenNotificationParams

((data?: UpdateResponse<TQueryFnData>

CreateResponse<TQueryFnData>, values?: TVariables

... 1 more ..., resource?: string

undefined) => false

OpenNotificationParams)

undefined

Success notification configuration to be displayed when the mutation is successful.

'"There was an error creating resource (status code: statusCode)" or "Error when updating resource (status code: statusCode)"'

errorNotification

false

OpenNotificationParams

((error?: TError, values?: TVariables

{ id: BaseKey; values: TVariables; }, resource?: string

undefined) => false

OpenNotificationParams)

undefined

Error notification configuration to be displayed when the mutation fails.

'"There was an error creating resource (status code: statusCode)" or "Error when updating resource (status code: statusCode)"'

action

"create" | "edit" | "clone"

Type of the form mode

Action that it reads from route otherwise "create" is used

autoSave

{ enabled: boolean; debounce?: number; onFinish?: ((values: TVariables) => TVariables); invalidateOnUnmount?: boolean

undefined; invalidateOnClose?: boolean

undefined; }

undefined

autoSubmitClose

boolean

When true, row will be closed after successful submit.

true

Type Parameters

PropertyDescriptionTypeDefault
TQueryFnDataResult data returned by the query function. Extends BaseRecordBaseRecordBaseRecord
TErrorCustom error object that extends HttpErrorHttpErrorHttpError
TVariablesValues for params{}
TSearchVariablesValues for search params{}
TDataResult data returned by the select function. Extends BaseRecord. If not specified, the value of TQueryFnData will be used as the default value.BaseRecordTQueryFnData

Return values

PropertyDescriptionType
searchFormPropsAnt Design <Form> propsFormProps<TSearchVariables>
tablePropsAnt Design <Table> propsTableProps<TData>
tableQueryResultResult of the react-query's useQueryQueryObserverResult<{`` data: TData[];`` total: number; },`` TError>
sorterCurrent sorting stateCrudSorting
filtersCurrent filters stateCrudFilters
formAnt Design <Form> instanceFormInstance
formPropsAnt Design <Form> propsFormProps
saveButtonPropsProps for a submit button{ disabled: boolean; onClick: () => void; }
cancelButtonPropsProps for a cancel button{ onClick: () => void; }
editButtonPropsProps for an edit button{ onClick: () => void; }
queryResultResult of the query of a recordQueryObserverResult<T>
mutationResultResult of the mutation triggered by submitting the formUseMutationResult<T>
formLoadingLoading state of form requestboolean
idRecord id for edit actionBaseKey
setIdid setterDispatch<SetStateAction< BaseKey | undefined>>
isEditingCheck if is editing(id: BaseKey) => boolean

Example

Run on your local
npm create refine-app@latest -- --example table-antd-use-editable-table