useUpdate
useUpdate
provides a callback to call dataProvider.update()
on demand and update a single record based on its id
and a data
argument. It uses React-query’s useMutation
hook under the hood.
Syntax
useUpdate
requires no arguments and returns an array with a callback and a mutation state. Set the update resource
and params
when calling the callback:
const [update, { isPending }] = useUpdate();
const handleClick = () => {
update(resource, params, options);
};
Alternatively, you can pass the arguments at definition time and call the callback without arguments:
const [update, { isPending }] = useUpdate(resource, params, options);
const handleClick = () => {
update();
};
It’s up to you to pick the syntax that best suits your component. If you have the choice, we recommend using the first syntax.
The params
argument is an object that lets you specify the id
of the record to update and the new data for the record. It can optionally specify the previousData
and a meta
parameter.
update('post', { id: 123, data: { isPublished: true } });
The options
argument is optional.
Usage
Here is an example of a LikeButton
component that increments the likes
field of a record when clicked:
import { useUpdate, useRecordContext } from 'react-admin';
const LikeButton = () => {
const record = useRecordContext();
const [update, { isPending }] = useUpdate();
const notify = useNotify();
const handleClick = () => {
if (!record) throw new Error('LikeButton must be called with a RecordContext');
const data = { likes: record.likes + 1 };
update(
'posts',
{ id: record.id, data, previousData: record };
{
onSuccess: () => {
notify('Like updated');
},
onError: (error) => {
notify('Error: like not updated', { type: 'error' });
},
}
)
}
return <button disabled={isPending} onClick={handleClick}>Like</button>;
};
Params
The second argument of the useUpdate
hook is an object with the following properties:
id
: the identifier of the record to update,data
: the new data for the record,previousData
: the record before the update (optional),meta
: an object to pass additional information to the dataProvider (optional).
const IncreaseLikeButton = () => {
const record = useRecordContext();
const [update] = useUpdate();
const handleClick = () => {
if (!record) throw new Error('LikeButton must be called with a RecordContext');
const params = {
id: record.id,
data: { likes: record.likes + 1 },
previousData: record
};
update('posts', params);
}
return <button onClick={handleClick}>Like</button>;
};
id
should be the identifier of the record to update. If it’s empty, the mutation will fail.
data
can be the complete record or just the fields to update. The data provider will merge the new data with the existing record.
previousData
should be the current record value. It’s useful for data providers that need to compute a diff to use a PATCH
request instead of a PUT
request. React-admin components systematically include this parameter when calling the update
callback.
meta
is helpful for passing additional information to the dataProvider. For instance, you can pass the current user to let a server-side audit system know who made the change.
Options
useUpdate
’s third parameter is an options
object with the following properties:
mutationMode
,onError
,onSettled
,onSuccess
,returnPromise
.
const notify = useNotify();
const redirect = useRedirect();
const [update, { isPending }] = useUpdate(
'comments',
{ id: record.id, data: { isApproved: true } },
{
mutationMode: 'optimistic',
onSuccess: () => {
notify('Comment approved');
redirect('/comments');
},
onError: (error) => {
notify(`Comment approval error: ${error.message}`, { type: 'error' });
},
}
);
Additional options are passed to React Query’s useMutation
hook. This includes:
gcTime
,networkMode
,onMutate
,retry
,retryDelay
,mutationKey
,throwOnError
.
Check the useMutation documentation for a detailed description of all options.
Tip: In react-admin components that use useUpdate
, you can override the mutation options using the mutationOptions
prop. This is very common when using mutation hooks like useUpdate
, e.g., to display a notification or redirect to another page.
For instance, here is a button using <UpdateButton mutationOptions>
to notify the user of success or failure using the bottom notification banner:
import * as React from 'react';
import { UpdateButton, useNotify, useRedirect } from 'react-admin';
const ApproveButton = () => {
const notify = useNotify();
const redirect = useRedirect();
return <UpdateButton
label="Approve"
data={{ isApproved: true }}
mutationOptions={{
mutationMode: 'optimistic',
onSuccess: () => {
notify('Comment approved');
redirect('/comments');
},
onError: (error) => {
notify(`Comment approval error: ${error.message}`, { type: 'error' });
},
}}
/>;
};
The components that support mutation options are:
<Edit>
,<EditBase>
,<EditDialog>
,<EditInDialogButton>
,<Create>
,<CreateBase>
,<CreateDialog>
,<CreateInDialogButton>
,<SaveButton>
,<UpdateButton>
,<Calendar>
,<CompleteCalendar>
,<DatagridAG>
,<TreeWithDetails>
.
Return Value
The useUpdate
hook returns an array with two values:
- the
update
callback, and - a mutation state object with the following properties:
data
,error
,isError
,isIdle
,isPending
,isPaused
,isSuccess
,failureCount
,failureReason
,mutate
,mutateAsync
,reset
,status
,submittedAt
,variables
.
The update
callback can be called with a resource
and a param
argument, or, if these arguments were defined when calling useUpdate
, with no argument at all:
// Option 1: define the resource and params when calling the callback
const [update, { isPending }] = useUpdate();
const handleClick = () => {
update(resource, params, options);
};
// Option 2: define the resource and params when calling the hook
const [update, { isPending }] = useUpdate(resource, params, options);
const handleClick = () => {
update();
};
For a detailed description of the mutation state, check React-query’s useMutation
documentation.
Since useUpdate
is mainly used in event handlers, success and error side effects are usually handled in the onSuccess
and onError
callbacks. In most cases, the mutation state is just used to disable the update button while the mutation is pending.
mutationMode
The mutationMode
option lets you switch between three rendering modes, which change how the success side effects are triggered:
pessimistic
(the default)optimistic
, andundoable
Here is an example of using the optimistic
mode:
const [update, { data, isPending, error }] = useUpdate(
'comments',
{ id: record.id, data: { isApproved: true } },
{
mutationMode: 'optimistic',
onSuccess: () => { /* ... */},
onError: () => { /* ... */},
}
);
In pessimistic
mode, the onSuccess
side effect executes after the dataProvider responds.
In optimistic
mode, the onSuccess
side effect executes just before the dataProvider.update()
is called, without waiting for the response.
In undoable
mode, the onSuccess
side effect fires immediately. The actual call to the dataProvider is delayed until the update notification hides. If the user clicks the undo button, the dataProvider.update()
call is never made.
See Optimistic Rendering and Undo for more details.
Tip: If you need a side effect to be triggered after the dataProvider response in optimistic
and undoable
modes, use the onSettled
callback.
onError
The onError
callback is called when the mutation fails. It’s the perfect place to display an error message to the user.
const notify = useNotify();
const [update, { data, isPending, error }] = useUpdate(
'comments',
{ id: record.id, data: { isApproved: true } },
{
onError: () => {
notify('Error: comment not approved', { type: 'error' });
},
}
);
Note: If you use the retry
option, the onError
callback is called only after the last retry has failed.
onSettled
The onSettled
callback is called at the end of the mutation, whether it succeeds or fails. It will receive either the data
or the error
.
const notify = useNotify();
const [update, { data, isPending, error }] = useUpdate(
'comments',
{ id: record.id, data: { isApproved: true } },
{
onSettled: (data, error) => {
// ...
},
}
);
Tip: The onSettled
callback is perfect for calling a success side effect after the dataProvider response in optimistic
and undoable
modes.
onSuccess
The onSuccess
callback is called when the mutation succeeds. It’s the perfect place to display a notification or to redirect the user to another page.
const notify = useNotify();
const redirect = useRedirect();
const [update, { data, isPending, error }] = useUpdate(
'comments',
{ id: record.id, data: { isApproved: true } },
{
onSuccess: () => {
notify('Comment approved');
redirect('/comments');
},
}
);
In pessimistic
mutation mode, onSuccess
executes after the dataProvider.update()
responds. React-admin passes the result of the dataProvider.update()
call as the first argument to the onSuccess
callback.
In optimistic
mutation mode, onSuccess
executes before the dataProvider.update()
is called, without waiting for the response. The callback receives no argument.
In undoable
mutation mode, onSuccess
executes before the dataProvider.update()
is called. The actual call to the dataProvider is delayed until the update notification hides. If the user clicks the undo button, the dataProvider.update()
call is never made. The callback receives no argument.
returnPromise
By default, the update
callback that useUpdate
returns is synchronous and returns nothing. To execute a side effect after the mutation has succeeded, you can use the onSuccess
callback.
If this is not enough, you can use the returnPromise
option so that the update
callback returns a promise that resolves when the mutation has succeeded and rejects when the mutation has failed.
This can be useful if the server changes the record, and you need the updated data to update another record.
const [update] = useUpdate(
'posts',
{ id: record.id, data: { isPublished: true } },
{ returnPromise: true }
);
const [create] = useCreate('auditLogs');
const publishPost = async () => {
try {
const post = await update();
create('auditLogs', { data: { action: 'publish', recordId: post.id, date: post.updatedAt } });
} catch (error) {
// handle error
}
};
TypeScript
The useUpdate
hook accepts a generic parameter for the record type and another for the error type:
useUpdate<Product, Error>(undefined, undefined, {
onError: (error) => {
// TypeScript knows that error is of type Error
},
onSettled: (data, error) => {
// TypeScript knows that data is of type Product
// TypeScript knows that error is of type Error
},
})