Search code examples
reactjsjsonschemareact-jsonschema-forms

react-jsonschema-form custom element position


Using react-jsonschema-form [1] we can dynamically render React components from configuration rather than code. I.e.

const Form = JSONSchemaForm.default;
const schema = {
  title: "Test form",
  type: "object",
  properties: {
    name: {
      type: "string"
    },
    age: {
      type: "number"
    }
  }
};

ReactDOM.render((
  <Form schema={schema} />
), document.getElementById("app"));

And we can use uiSchema object to customize and configure the individual look of each component. Is it possible however, instead of having the form created in a linear fashion, can I obtain each individual element and position them where I want?

For example, I may want the name field on page1 and the age field on page3. The only way I can think to do this currently is editing the package's source to return individual elements rather than wrapping them into the Form object.

Maybe there is some way in React to interrogate and extract the individual components from the Form object?

Thanks

[1] https://react-jsonschema-form.readthedocs.io/en/latest/


Solution

  • No, there isn't a way to get rjsf to split a form into multiple UI components. You can emulate that behavior by making multiple forms (multiple JSON Schema schemas). Depending on the structure of the combined output you want, it may be simplest to break the schema down by different nested property apparent at the root level, e.g. the 1st page corresponds to the 1st key at the root level (name), the 2nd page corresponds to the 2nd key (age), and so on. Note that you'd have to control this pagination and navigation yourself, and also merge each page's formData back into the singular payload you were expecting.

    Here's a rough draft:

    pageOneSchema = {
      title: "Test form pg. 1",
      type: "object",
      properties: {
        name: {
          type: "string"
        }
      }
    };
    
    pageTwoSchema = {
      title: "Test form pg. 2",
      type: "object",
      properties: {
        age: {
          type: "number"
        }
      }
    };
    
    const schemas = [pageOneSchema, pageTwoSchema];
    
    // Your component's member functions to orchestrate the form and its "pages"
    state = {
      currentPage: 0,
      pages: new Array(schemas.length).fill({}), // initial pages array to empty objects = unfilled forms
    }
    
    pageDataChangeHandler = (page) => {
      const onChange = (e) => {
        const pages = [...this.state.pages];
        pages[page] = e.formData; // not too 100% about the key
        this.setState({
          pages,
        });
      }
    }
    
    getSubmitHandlerForPage = (page) => {
      if (page !== schemas.length) {
        // If current page is not the last page, move to next page
        this.setState({
          currentPage: page + 1,
        });
      } else {
        // concat all the pages' data together to recompose the singular payload
      }
    }
    
    ...
    const { currentPage } = this.state; 
    <Form
      formData={this.state.pages[currentPage]}
      schema={schemas[currentPage]}
      onChange={this.pageDataChangeHandler(currentPage)}
      onSubmit={this.getSubmitHandlerForPage(currentPage)}
    />
    

    You can also pass children into the Form component to render your own buttons (so maybe you don't want the forms saying "Submit" instead of "Next Page" if they're not actually going to submit).

    Note that if you do custom buttons and make a "Next" button, it must still be of "submit" type because of the type of event it emits (used for validation and some other things). Though there was an ask to make the text/title of the submit button easier to manipulate...