I have two resources: Hardware
and Product
.
The hardware depends on the Product.
All examples suggest using one Create/Edit
form for one resource.
Can I change this behavior?
For better UX, I want to create a product seamlessly. Users should see just one form with inputs from the two resources.
Of course, the product
must be created before using it's identifier as a foreign key while creating hardware
.
Do you have similar examples?
All examples suggest using one Create/Edit form for one resource.
Can I change this behavior?
The wonderful team at Marmelab write the documentation to focus on showing the key aspects so that you appreciate the basic building blocks of react-admin
.
But yes, it's possible to one Create/Edit
form with multiple resources.
And all the magic lies in the Toolbar, and here's what the team says:
At the bottom of the form, the toolbar displays the submit button.
You can override this component by setting thetoolbar
prop, to display the buttons of your choice.
And once you have buttons of your choice, you have all the freedom to handle multiple <Resource>
s (of course, you must declare those resources within the <Admin>
component).
Here's an example of what i've done multiple times.
And i'll use it in context of - hardware
and product
- your resources.
import React from 'react';
import * as request from 'superagent'; // HTTP request library
import { useHistory } from 'react-router-dom';
import { useFormState } from 'react-final-form'; // react-admin forms are based on this
import {
Create,
SaveButton,
SimpleForm,
Toolbar,
useRedirect,
useNotify
} from 'react-admin';
// main "create' component
export const ProductCreate = props => {
return (
<Create {...props}>
// we override the "toolbar" prop by passing a custom toolbar
<SimpleForm toolbar={<ProductCreateToolbar/>} {...props}>
...
</SimpleForm>
</Create>
);
};
// Handling "create" mode
const ProductCreateToolbar = props => {
return (
<Toolbar {...props}>
<ProductSaveButton {...props} />
</Toolbar>
)
};
// define the "save/create" button
const ProductSaveButton = props => {
const formState = useFormState();
const redirectTo = useRedirect();
const notify = useNotify();
const token = sessionStorage.getItem('token'); // often created after "login" success
const {
// form fields related to "product" e.g.
productName,
productSize,
productCost,
// form fields related to "hardware" e.g.
hardwareName,
hardwareLocation
...other
} = formState && formState.values;
// Note: useFormState() gets fields based on the "source" prop of the inputs.
const handleSave = () => {
// based on any HTTP request library e.g. superagent
apiCall(
{ ... }, // data to create "product"
token, // needed if the endpoints are protected
'post', // basically the HTTP POST
'products/' // replace with actual endpoint to create a product
).then(res => {
// check for a response
if (response) {
const { status, body } = res;
// if the "product" has been created
if (status === 200 || status === 201) {
// pick the "product id"
// or specific foreign-key to create a "hardware"
let productId = body['id'];
apiCall(
{ product_id: productId, ...other } // data to create "hardware"
token,
'post',
'hardwares/' // replace with actual endpoint to create a hardware
).then(res => {
if (res) {
const { status } = res;
if (status === 200 || status === 201) {
// display toast message to user
notify(`Product and hardware have been created successfully`, "info");
// redirect to products list
redirectTo('list', basePath);
} else {
// any action if create fails
};
};
}).catch(
error => console.error('Error while saving:', error)
);
};
};
}).catch(
error => console.error('Error while saving:', error)
);
}
return (
<SaveButton
{...props}
label="Save"
submitOnEnter={false}
saving={loading}
onSave={handleSave} // handles the create
/>
);
};
// for making the HTTP requests (used within the toolbar buttons)
const apiCall = (data, header, type, url) => {
return new Promise((resolve, reject) => {
request[type](`http://localhost:8000/api/${url}`)
.send(data)
.set('Content-Type', 'application/json')
.set('authorization', 'Bearer ' + header)
.end((err, res) => {
if (res) {
return resolve(res.body);
}
return reject(err);
});
});
};
Please note that react-admin
provides specialized hooks.
And you can do that above by calling the useCreate
hook twice (for "product" and 'hardware") but you WILL NOT have as much control over the process as what I have demonstrated above.