Add/Edit multiple resources in one form

Viewed 914

I am trying to manage two linked resources in one Edit > SimpleForm. The relationship between both resources is a one-to-one between two tables.

For instance, let's say we have these two tables:

User

  • id
  • email
  • password
  • name
  • address_id

Address

  • id
  • street
  • number
  • postcode
  • country_name

I'm using react-admin for managing records from these two tables from PostgreSQL through graphql (using hasura). There are a few options in term of data provider for hasura and graphql.

The first one I tried ra-data-hasura works using the /v1/query REST API endpoint of hasura. The documentation for this endpoint is lacking.

I also tried ra-data-graphql-simple, and ra-data-hasura-graphql.

As far as I can tell, none support querying relations/foreign key tables in one query. Therefore none of these provider can retrieve foreign keys tables and embed the result directly in the main object response json.

Just to be clear, I mean returning something like this for user:

{
 "id": 1,
 "name": "John Smith",
 "email": "john.smith@gmail.com",
 "password": "password",
 "address": {
   "id": 145,
   "street": "High Street",
   "number": "25b",
   "postcode": "EC2 5FG",
   "country_name": "United Kingdom"
 }
}

Therefore I am trying to retrieve the address record on the Edit form using a separate query, which I did (probably not in the best way, feel free to suggest a better version):

import { useGetOne, Loading, Error } from 'react-admin';

const FetchRelated = ({ record, reference, source, children }) => {
    const { data, loading, error } = useGetOne(reference, record[source]);
    if (loading) return <Loading />;
    if (error) return <Error />;
    if (!data) return null;

    // this is the only way I found to be able to populate the fields in the form with the address data
    record[reference] = data;

    return <React.Fragment>{children}</React.Fragment>;
};

...
import { Edit, SimpleForm, TextInput } from 'react-admin';

...
const UserEdit = props => (
 <Edit {...props}>
  <SimpleForm>
   <FetchRelated reference="address" source="address_id">
    <TextInput source="address.postcode" label="Post Code" />
   </FetchRelated>
  </SimpleForm>
 </Edit>
);

Using this, my record object now looks like this:

{
 "id": 1,
 "name": "John Smith",
 "email": "john.smith@gmail.com",
 "password": "password",
 "address_id": 145,
 "address": {
   "id": 145,
   "street": "High Street",
   "number": "25b",
   "postcode": "EC2 5FG",
   "country_name": "United Kingdom"
 }
}

Now I have to take care of the actual edit query. It seems react-admin uses useEditController for all the requests triggering (both useGetOne and update). While the update method is passed as save attribute to SimpleForm, it's not passed to Toolbar, which means (from what I can tell) one cannot override the handleSubmit/handleSubmitWithRedirect props of SaveButton without having to essentially replicate all the code for update from useEditController.

Ideally, one could override the handleSubmit and do one update for both resources at once. Using a graphql mutation doing that wouldn't be too much trouble, unfortunately using hasura /v1/graphql endpoint doesn't provide a way to update relations in one query. Therefore we need to do two queries, which is fine.

Ideally I would want to re-use the existing update method to update the user record, and add another update call for the address.

What is the best possible approach to achieve something like this?

import React, { useCallback } from 'react';
import { useForm } from 'react-final-form';
import { useSelector } from 'react-redux';
import { Toolbar, SaveButton, DeleteButton, useMutation } from 'react-admin';

const RelatedSaveButton = ({ resourceName, id, handleSubmitWithRedirect, ...props }) => {
  const form = useForm();
  const stateData = useSelector(
        state =>
             state.admin.resources[resourceName]
                 ? state.admin.resources[resourceName].data
                 : null,
         [resourceName]
     );
  const [update] = useMutation();
  const handleSave = useCallback((values, redirect) => {
    const params = {type: 'update', resource: resourceName, payload: {id: id, data: values[resourceName], previousData: stateData}};
    update(params, {onSuccess: response => {
      // doing something here to update `user` using `save` from `useEditController`
      // handleSubmitWithRedirect(redirect);
    }});
  }, [resourceName, stateData, id, update]);

  return <SaveButton {...props} onSave={handleSave} />;
};

const UserToolbar = ({ ...props }) => (
  <Toolbar {...props}>
    <RelatedSaveButton resourceName='address' id={props.record['address_id']} />
    <DeleteButton />
  </Toolbar>
);
0 Answers
Related