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:

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, and
  • undoable

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
    },
})