Search code examples
reactjsreact-admin

React admin: Can I create two resources using one form?


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?


Solution

  • 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 the toolbar 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.