Provide an easier way to manage both PUT and PATCH methods
See original GitHub issueIs your feature request related to a problem? Please describe.
I have a FeathersJS application that allows to define a PUT and a PATCH method handlers per service.
I define a rides
service that use both of them: The PUT (update) method for data changes and the PATCH method for quick actions.
As the default react-admin data provider only support one way to update a record (update
), I did my own calling the right method according to the used service:
dataProvider.ts
import {
DataProvider as BaseDataProvider,
GetListParams,
} from 'react-admin';
import {
Service,
} from '@kidways/api';
import {
diff,
} from '../utils';
import { client } from './client';
// Make typescript happy with service keys.
const getClientService = (resource: string): Service<any> => client.service(resource);
type FeathersSort = { [key: string]: 1 | -1; };
const oneResponseFormat = (response: any): any => ({
data: {
id: response._id,
...response,
},
});
const manyResponseFormat = (response: any): any => {
let data;
if (!response.data) {
response.total = response.length;
data = response;
} else {
data = response.data;
}
response.data = data.map((_item: any) => {
const item = _item;
item.id = _item._id;
return _item;
});
return response;
};
const clientFind = (resource: string, { pagination, filter, sort }: GetListParams): any => {
const resolvedSort: FeathersSort = {};
resolvedSort[sort.field] = sort.order === 'ASC' ? 1 : -1;
return getClientService(resource)
.find({
query: {
...filter,
$sort: resolvedSort,
$limit: pagination.perPage,
$skip: pagination.perPage * (pagination.page - 1),
},
})
.then(manyResponseFormat);
};
const needUpdateResources = [
// ... another services that need to use the update method.
'rides',
];
interface DataProvider extends BaseDataProvider {
patch: BaseDataProvider['update'];
}
const patch: DataProvider['patch'] = (resource, { id, data, previousData }) => getClientService(resource)
.patch(id, diff(data, previousData))
.then(oneResponseFormat);
export const dataProvider: DataProvider = {
getList: (resource, params) => clientFind(resource, params),
getOne: (resource, { id }) => getClientService(resource).get(id).then(oneResponseFormat),
getMany: (resource, { ids }) => getClientService(resource).find({
query: {
_id: { $in: ids },
},
}).then(manyResponseFormat),
getManyReference: (resource, {
target, id, filter, ...params
}) => clientFind(resource, {
filter: {
...filter,
[target]: id,
},
...params,
}),
create: (resource, { data }) => getClientService(resource).create(data).then(oneResponseFormat),
update: (resource, { id, data, ...rest }) => {
if (!needUpdateResources.includes(resource)) {
return patch(resource, {
id,
data,
...rest,
});
}
return getClientService(resource)
.update(id, (({ id: _, _id, ...dataRest }) => dataRest)(data))
.then(oneResponseFormat);
},
patch,
updateMany: () => { throw new Error('not implemented'); },
delete: (resource, { id }) => getClientService(resource).remove(id).then(oneResponseFormat),
deleteMany: (resource, { ids }) => Promise
.all(ids.map((id) => getClientService(resource).remove(id)))
.then(manyResponseFormat),
};
export default null;
With this provider, the rides
service will use the update
method by default.
For the quick actions buttons that need the patch
method, I did specify it on the useMutation
hook provided in react-admin v3:
StartButton.tsx
import React, {
FC,
} from 'react';
import {
ButtonProps,
useMutation,
useNotify,
useRefresh,
} from 'react-admin';
import {
PlayCircleFilledOutlined as StartIcon,
} from '@material-ui/icons';
import {
WithConfirmationButton,
} from '../../../../components';
export const StartButton: FC<ButtonProps> = ({ record }) => {
const notify = useNotify();
const refresh = useRefresh();
const [start, { loading }] = useMutation(
{
resource: 'rides',
type: 'patch',
payload: {
id: record?.id,
data: { start: true },
},
},
{
onSuccess: () => {
notify('Conduite démarrée.');
refresh();
},
onFailure: (error) => {
notify(`Echec : ${error?.message}`, 'error');
refresh();
},
},
);
if (!record) {
return null;
}
return (
<WithConfirmationButton
label="Démarrer"
onConfirm={start}
disabled={loading}
>
<StartIcon />
</WithConfirmationButton>
);
};
export default null;
In React Admin v4, we have to replace useMutation
by useUpdate
.
As the useUpdate
calls the dataProvider.update
method directly, I don’t have the possibility to specify the patch
usage anymore.
I may directly use the dataProvider
, but I’ll lost all the useUpdate
inner logic based on react-query
mutations (Query caching, onSuccess
/onError
callbacks, the possibility to pass the parameters directly on the callback…).
Describe the solution you’d like
Having a as simple as the v3 way to specify which method of the data provider to call on the useUpdate
function:
useUpdate(
'rides',
{
id: 42,
data: {
// ...
}
},
{
method: 'patch' // 'update'|'patch' or a simply a string.
}
);
We may also consider to introduce a DataProvider.patch
optional method definition and its related usePatch
hook.
Describe alternatives you’ve considered
As the dataProvider.update
method is called in
the middle of the useUpdate
hook,
the only wat I found so far is to do a complete copy/paste of this hook onto a usePatch
hook, just to call dataProvider.patch
instead.
It works. However, copying ~400 lines of vendor code just to change one of them is very cumbersome and may lead to upgrade maintenance hell situations.
By the way, if you have a simpler workaround, I’am looking for that! 👍
Additional context
N/A
Issue Analytics
- State:
- Created a year ago
- Comments:7 (7 by maintainers)
I mean the possibility to define the type of the
meta
parameter as with can define the data record and mutation error:https://github.com/marmelab/react-admin/blob/24cd6b4df6b5abb457520ea0b5433d82447cea86/packages/ra-core/src/types.ts#L173
Currently not accessible here:
https://github.com/marmelab/react-admin/blob/24cd6b4df6b5abb457520ea0b5433d82447cea86/packages/ra-core/src/types.ts#L175
Hi, and thanks for sharing your idea.
Matching CRUD events with HTTP verbs is the job of the data provider. Add your PUT/PATCh logic there, and use either react-admin’s useUpdate (if you put the logic in data provider.update), or react-query’s useMutation (if you put the logic in a custom data provider method).
We won’t support passing a custom HTTP verb to useUpdate. You have the meta parameter for that.