UNPKG

ra-core

Version:

Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React

188 lines 8.88 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useUpdate = void 0; const react_query_1 = require("@tanstack/react-query"); const useDataProvider_1 = require("./useDataProvider.cjs"); const useMutationWithMutationMode_1 = require("./useMutationWithMutationMode.cjs"); const util_1 = require("../util/index.cjs"); /** * Get a callback to call the dataProvider.update() method, the result and the loading state. * * @param {string} resource * @param {Params} params The update parameters { id, data, previousData, meta } * @param {Object} options Options object to pass to the queryClient. * May include side effects to be executed upon success or failure, e.g. { onSuccess: () => { refresh(); } } * May include a mutation mode (optimistic/pessimistic/undoable), e.g. { mutationMode: 'undoable' } * * @typedef Params * @prop params.id The resource identifier, e.g. 123 * @prop params.data The updates to merge into the record, e.g. { views: 10 } * @prop params.previousData The record before the update is applied * @prop params.meta Optional meta data * * @returns The current mutation state. Destructure as [update, { data, error, isPending }]. * * The return value updates according to the request state: * * - initial: [update, { isPending: false, isIdle: true }] * - start: [update, { isPending: true }] * - success: [update, { data: [data from response], isPending: false, isSuccess: true }] * - error: [update, { error: [error from response], isPending: false, isError: true }] * * The update() function must be called with a resource and a parameter object: update(resource, { id, data, previousData }, options) * * This hook uses react-query useMutation under the hood. * This means the state object contains mutate, isIdle, reset and other react-query methods. * * @see https://react-query-v3.tanstack.com/reference/useMutation * * @example // set params when calling the update callback * * import { useUpdate, useRecordContext } from 'react-admin'; * * const IncreaseLikeButton = () => { * const record = useRecordContext(); * const diff = { likes: record.likes + 1 }; * const [update, { isPending, error }] = useUpdate(); * const handleClick = () => { * update('likes', { id: record.id, data: diff, previousData: record }) * } * if (error) { return <p>ERROR</p>; } * return <button disabled={isPending} onClick={handleClick}>Like</div>; * }; * * @example // set params when calling the hook * * import { useUpdate, useRecordContext } from 'react-admin'; * * const IncreaseLikeButton = () => { * const record = useRecordContext(); * const diff = { likes: record.likes + 1 }; * const [update, { isPending, error }] = useUpdate('likes', { id: record.id, data: diff, previousData: record }); * if (error) { return <p>ERROR</p>; } * return <button disabled={isPending} onClick={() => update()}>Like</button>; * }; * * @example // TypeScript * const [update, { data }] = useUpdate<Product>('products', { id, data: diff, previousData: product }); * \-- data is Product */ const useUpdate = (resource, params = {}, options = {}) => { const dataProvider = (0, useDataProvider_1.useDataProvider)(); const queryClient = (0, react_query_1.useQueryClient)(); const { mutationMode = 'pessimistic', getMutateWithMiddlewares, ...mutationOptions } = options; const dataProviderUpdate = (0, util_1.useEvent)((resource, params) => dataProvider.update(resource, params)); const [mutate, mutationResult] = (0, useMutationWithMutationMode_1.useMutationWithMutationMode)({ resource, ...params }, { ...mutationOptions, mutationKey: [resource, 'update', params], mutationMode, mutationFn: ({ resource, ...params }) => { if (resource == null) { throw new Error('useUpdate mutation requires a resource'); } if (params.id == null) { throw new Error('useUpdate mutation requires a non-empty id'); } if (!params.data) { throw new Error('useUpdate mutation requires a non-empty data object'); } return dataProviderUpdate(resource, params); }, updateCache: ({ resource, ...params }, { mutationMode }, result) => { // hack: only way to tell react-query not to fetch this query for the next 5 seconds // because setQueryData doesn't accept a stale time option const now = Date.now(); const updatedAt = mutationMode === 'undoable' ? now + 5 * 1000 : now; // Stringify and parse the data to remove undefined values. // If we don't do this, an update with { id: undefined } as payload // would remove the id from the record, which no real data provider does. const clonedData = JSON.parse(JSON.stringify(mutationMode === 'pessimistic' ? result : params?.data)); const updateColl = (old) => { if (!old) return old; const index = old.findIndex( // eslint-disable-next-line eqeqeq record => record.id == params?.id); if (index === -1) { return old; } return [ ...old.slice(0, index), { ...old[index], ...clonedData }, ...old.slice(index + 1), ]; }; const previousRecord = queryClient.getQueryData([ resource, 'getOne', { id: String(params?.id), meta: params?.meta }, ]); queryClient.setQueryData([ resource, 'getOne', { id: String(params?.id), meta: params?.meta }, ], (record) => ({ ...record, ...clonedData, }), { updatedAt }); queryClient.setQueriesData({ queryKey: [resource, 'getList'] }, (res) => res && res.data ? { ...res, data: updateColl(res.data) } : res, { updatedAt }); queryClient.setQueriesData({ queryKey: [resource, 'getInfiniteList'] }, (res) => res && res.pages ? { ...res, pages: res.pages.map(page => ({ ...page, data: updateColl(page.data), })), } : res, { updatedAt }); queryClient.setQueriesData({ queryKey: [resource, 'getMany'] }, (coll) => coll && coll.length > 0 ? updateColl(coll) : coll, { updatedAt }); queryClient.setQueriesData({ queryKey: [resource, 'getManyReference'] }, (res) => res && res.data ? { ...res, data: updateColl(res.data) } : res, { updatedAt }); const optimisticResult = { ...previousRecord, ...clonedData, }; return optimisticResult; }, getQueryKeys: ({ resource, ...params }) => { const queryKeys = [ [ resource, 'getOne', { id: String(params?.id), meta: params?.meta }, ], [resource, 'getList'], [resource, 'getInfiniteList'], [resource, 'getMany'], [resource, 'getManyReference'], ]; return queryKeys; }, getMutateWithMiddlewares: mutationFn => { if (getMutateWithMiddlewares) { // Immediately get the function with middlewares applied so that even if the middlewares gets unregistered (because of a redirect for instance), // we still have them applied when users have called the mutate function. const mutateWithMiddlewares = getMutateWithMiddlewares(dataProviderUpdate.bind(dataProvider)); return args => { // This is necessary to avoid breaking changes in useUpdate: // The mutation function must have the same signature as before (resource, params) and not ({ resource, params }) const { resource, ...params } = args; return mutateWithMiddlewares(resource, params); }; } return args => mutationFn(args); }, }); const update = (0, util_1.useEvent)((callTimeResource = resource, callTimeParams = {}, callTimeOptions = {}) => { return mutate({ resource: callTimeResource, ...callTimeParams, }, callTimeOptions); }); return [update, mutationResult]; }; exports.useUpdate = useUpdate; //# sourceMappingURL=useUpdate.js.map