Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to trigger form submit/validation externally from the form #500

Closed
2 tasks done
aackerman opened this issue Mar 1, 2017 · 38 comments
Closed
2 tasks done

Unable to trigger form submit/validation externally from the form #500

aackerman opened this issue Mar 1, 2017 · 38 comments
Labels
possibly close To confirm if this issue can be closed

Comments

@aackerman
Copy link

aackerman commented Mar 1, 2017

Prerequisites

  • I have read the documentation;
  • In the case of a bug report, I understand that providing a SSCCE example is tremendously useful to the maintainers.

Description

Requirements from my design team require the submit buttons to be logically separate in structure, both is usage of React and the DOM. I'm looking for a way to submit and/or validate the form externally or pass in errors that I have validated externally. It doesn't seem possible right now and I couldn't find a way to do it that didn't seem like a complete hack relying on the internals of RJSF.

Steps to Reproduce

The following is essentially the crux of the structure. Our design team wants the buttons to be above the form in a header instead of below and the structure of the application prevents wrapping the header inside the form. Any help with the ability to submit/validate/display errors without having buttons as children of the form would be useful.

<div>
  <Header>
    <button>Submit</button>
    <button>Cancel</button>
  </Header>
  <Form {...formProps}>
    <span/>
  </Form>
</div>

Version

0.41.2

@n1k0
Copy link
Collaborator

n1k0 commented Mar 1, 2017

I'm looking for a way to submit and/or validate the form externally or pass in errors that I have validated externally

While external submission isn't supported, the lib has support for custom validation.

Our design team wants the buttons to be above the form in a header instead of below and the structure of the application prevent wrapping the header inside the form

This is rather weird semantically speaking. I'd probably try to position the regular form children buttons using CSS, but I'm not in your shoes.

@glasserc
Copy link
Contributor

glasserc commented Mar 2, 2017

Related to #155.

@elisechant
Copy link
Contributor

elisechant commented Mar 4, 2017

@aackerman

Some ideas:

  1. you might be able to make 2 linked forms with the same ui:rootFieldId and then use css or some other magic to hide and show the bits you need? https://github.com/mozilla-services/react-jsonschema-form#autogenerated-widget-ids

  2. You might be able to transclude the buttons and then use callback refs to trigger click on the right buttons:

const YourForm = () => {
  return (
    <div>
      <button onClick={() => this.submitBtn.click()}>Submit from above</button>
      <Form ref={(el => this.form = el)}
            schema={{foo:'bar'}}>
        <button type="submit" ref={(el => this.submitBtn = el)}>Submit</button>
      </Form>
    </div>
  )
};

  1. Or you might be able to artificially trigger form submission also using callback ref on the form itself:
const YourForm = () => {
  return (
    <div>
      <button onClick={() => this.form.submit()}>Submit from above</button>
      <Form ref={(el => this.form = el)}
            schema={{foo:'bar'}} />
    </div>
  )
};

Not tested.

@lucaas
Copy link

lucaas commented Mar 9, 2017

In our application we also have found the need to trigger form submission externally.

The form is presented in a dialog where the primary actions (submit) is in the dialog footer, and not part of the form which is in the dialog body. I also want to submit the form programatically when the user presses Ctrl/Cmd+Enter.

I've tried the following approaches to work around the problem:

  1. Calling submit on the HTMLFormElement.
  2. Create a custom submit event and dispatching it on the form.
  3. Adding a hidden input[type=submit] to the form, and calling click() on it.

Alternatives 1, 2 caused a page reload in Firefox, but alternative 3 seems to work so far.

It would be nice if this was supported by this library in the future.

@aackerman
Copy link
Author

Thanks for the input @lucaas. I had tried the first two and were unacceptable but I didn't get around to trying number 3.

@spacebaboon
Copy link
Contributor

We've done something like Lucaas' number 3. We render our own submit button as a child of the Form with a ref, then get the element by its ref and click() it.

@n1k0
Copy link
Collaborator

n1k0 commented Mar 22, 2017

@spacebaboon sounds good. Would you be willing to create a jsfiddle and contribute it to the Tips & Tricks section?

@spacebaboon
Copy link
Contributor

@n1k0 sure thing :-) might be a few days till I can find time, though.

@seanmclucas
Copy link

Or, could we expose the underlying DOM form element as part of the API? e.g.

constructor() {
  this._assignFormRef = (formElement) => {
    this.formElement = formElement;
  };
}

customFormSubmissionHandler() {
  this.formElement.submit();
}

render() {
  <Form ref={this._assignFormRef} />
}

@spacebaboon
Copy link
Contributor

@n1k0 Here's the jsfiddle:
https://jsfiddle.net/spacebaboon/g5a1re63/

I hide the form's submit button with CSS, and provide a ref to it. I also use a ref to get the Form component itself, so the container component has a way to refer to the button, then just call click on it. The external control panel component receives a click handler prop that links up the two.

If you're happy with the fiddle, would you like me to add the link to the Tips and Tricks section and submit a PR?

@n1k0
Copy link
Collaborator

n1k0 commented Apr 2, 2017

There's also this example https://jsfiddle.net/n1k0/jt3poj2v/1/, but I like the one you suggest as it has a Form wrapper. Your call which one is better.

@spacebaboon
Copy link
Contributor

I guess I prefer mine, as the code is all in components, and it has a CSS border to visually illustrate that the components are separate, but yours is shorter. Up to you :)

@n1k0
Copy link
Collaborator

n1k0 commented Apr 3, 2017

Well I don't like mine outscopes the submit action, so yours is fine by me. Please send a PR :)

@spacebaboon
Copy link
Contributor

Done :)

@n1k0
Copy link
Collaborator

n1k0 commented Aug 8, 2017

Note: I've just learned about the ability to attach inputs external to the form with html5 https://www.impressivewebs.com/html5-form-attribute/

This might the easiest way to deal with this sort of issue.

@spacebaboon
Copy link
Contributor

That's really nice 😄

Unfortunately the support from everyone's favourite browser vendor isn't quite there yet
http://caniuse.com/#search=form

@redixhumayun
Copy link
Contributor

Will this work for validation as well? Just calling this.validate on the underlying element from the wrapper?

@redixhumayun
Copy link
Contributor

So I just had a look through the source code, and I noticed that there is no way to call the validate function directly apart from through the onSubmit or onChange functions. Is there any way you guys would consider adding the ability to only validate the code without requiring submission? Doesn't look it would be too much of a hassle, right? Or am I just completely underestimating the problem?

@anthony-bernardo
Copy link

Is this exemple (https://jsfiddle.net/spacebaboon/g5a1re63/) still working ? I cannot import this JSONSchemaForm.default

@spacebaboon
Copy link
Contributor

spacebaboon commented Aug 28, 2018 via email

@anthony-bernardo
Copy link

anthony-bernardo commented Aug 28, 2018

@spacebaboon
How did you import this line :

const RJSForm = JSONSchemaForm.default;

@praveen747
Copy link

@n1k0 @spacebaboon: it works both of your ways but at the particularly @spacebaboon case I don't see how the validation can apply on it when I tried at your js fiddle example it's not helping to validate,[liveValidate ={true}],
Scenario: I'm handling submit function outside the form with a button called Next which is purposed for two functions one to submit the form for that current page and the second function is an additional progress section on the top of the form, here I'm going attach source :

**class MarsForm extends Component {
constructor(props) {
super(props);
this.state = {
current: 0,
};

}
onSubmit = ({formData}) => {

  }

getSubmit = ({formData})=> {

}

handleOnChange(value) {
}

handleCancel() {
if (this.state.saveSuccess || this.isSaving.call(this)) return false;
this.props.onClose();
}

async componentDidMount() {
const { key } = this.props.match.params;
const { formData: data } = await Service.getData(key)
this.setState({ data });
}

next() {
const current = this.state.current + 1;
this.setState({current });
}
prev() {
const current = this.state.current - 1;
this.setState({ current });
}
render() {
const { current } = this.state;
return (




{steps.map(item => <Step key={item.title } title={item.title} />)}




{
current === steps.length - 1
&& <button class="btn btn-primary pull-right" style={{height: 40, marginTop: 5, marginLeft: 8 }} type="primary" onClick={() => { message.success('Processing complete!')}}>Done
}
{
current < steps.length - 1
&&
<button class="btn btn-primary pull-right" style={{height: 40, marginTop: 5, marginLeft: 8}} type="submit" onClick={(current) => {this.next(); }}>Next
}
{
current > 0
&& (
<button class="btn btn-primary pull-right " style={{height: 40, marginTop: 5, }} onClick={() => this.prev()}>
Previous

)
}


);

}**

@jduncanRadBlue
Copy link

@spacebaboon This is awesome. I can't seem to get it to work with one button and multiple forms. We have a tabbed display; each tab has a form. When the user clicks the main submit button in the parent component, I'd like the forms to all try and submit. Do you know a way to do that? Thanks!

@praveen747
Copy link

praveen747 commented Dec 19, 2018 via email

@jduncanRadBlue
Copy link

@praveen747 We'd like to avoid having a button on each step. We have a submit button but it lives outside the forms in the parent component. When the user clicks submit, we would like the separate forms to validate. I'm doing that by passing down a state value to componentWillReceiveProps. From there I call the ref to the form this.form.submitButton.click(); This is where the error happens.
I get the following error: An invalid form control with name='' is not focusable. I believe it is occurring b/c that particular form is not currently visible based on the tab that is being displayed. The error occurs on the tabs that are not currently active.

Anyone have any advice on how to move forward?

@StevenVerbiest
Copy link

Is there any way to collect the errors onSubmit? I'm also submitting multiple tabs via ref.onSubmit, but I need to invoke a callback after submission of all forms, but the ref.state.errors array is always empty on submit.

@epicfaace
Copy link
Member

epicfaace commented Jun 19, 2019

@StevenVerbiest usually, onSubmit is called only when there are no errors on the form. What exactly do you mean by your question?

@jduncanRadBlue
Copy link

@StevenVerbiest we found no way to collect errors on different tabs because if the tab is not rendered, it doesn't exist in the DOM. We were getting errors "An invalid form control with name=foo is not focusable".
We ended up using on change for text boxes and populating redux state with the values. I then have a required fields json object that I run through on submit and check for values there. I throw an error in the parent component and display it on the page. Thats the best we could come up with for what we need.

@StevenVerbiest
Copy link

@epicfaace I'm calling onSubmit programmatically, which triggers the validation, but there is no way of collecting any errors after submit on runtime. It seems the errors are set asynchronously?

@jduncanRadBlue my case is a bit different, because the tabs are rendered in my component. Do you manually validate your data or do u use the jsonschema-form validator?

I'll try to set up a test case at a later time.

@epicfaace
Copy link
Member

@StevenVerbiest how are you calling onSubmit programmatically? Can you send a code example?

@jduncanRadBlue
Copy link

@StevenVerbiest I use my own validation. The jsonschema-form didn't know what to do with fields that weren't rendered b/c the tab info wasn't in the dom. I kept getting the focus errors and gave up; decided to write my own.

@ievgennaida
Copy link

Is it possible to call submit from the external button?
I would appreciate any example!

@kapalkat
Copy link

So I was looking for a nice solution here. I could not use new logic implemented here because some other UI team already wrapped react-jsonschema-form in a functional component without exposing its reference point.

So my first idea was to use native HTML5 form control reference.

<JsonForm
  id="json-form"
  onChange={(e) => {
    setForm({ ...form, fdCopy: e.formData });
  }}
  onSubmit={onSubmit}
></JsonForm>;

<Button value="Start" type="submit" form="json-form" />;

This is working fine and there is nothing wrong with this solution but I was looking further to get things done in more reactive fashion.
I have noticed that react-jsonschema-form is exposing {children} prop which can be used as a form submitter.
Decided to create a reference to that {children} button. Ended up with following solution:

import React, { createRef } from "react";

const submitFormRef = createRef();

<JsonForm
  onChange={(e) => {
    setForm({ ...form, fdCopy: e.formData });
  }}
  onSubmit={onSubmit}
>
  <button ref={submitFormRef} type="submit" style={{ display: "none" }} />>
</JsonForm>;

<Button value="Start" onClick={() => submitFormRef.current.click()} />;

It's working like a charm!
Attaching here as maybe someone is looking for a similar thing.

@devo-wm
Copy link

devo-wm commented Apr 26, 2021

Worked a treat. Thanks for the tip @kapalkat.

Question, was the form passed to the JsonForm formData prop or just an empty object initialized upstream?

@zwilderrr
Copy link

zwilderrr commented May 26, 2021

Thought I would throw my hat in the ring and provide a more explicit solution
codesandbox

import React, { createContext, useContext, useState } from "react";
import { withTheme } from "react-jsonschema-form";

export const Context = createContext();

export default function App() {
  const [ref, setRef] = useState(null);

  return (
    <div>
      <Context.Provider value={{ setRef, ref }}>
        <FarAwayForm />
        <FarAwayButton />
      </Context.Provider>
    </div>
  );
}

function FarAwayForm() {
  return <MyForm />;
}

// The button
function FarAwayButton() {
  const { ref } = useContext(Context);
  return (
    <button type="submit" onClick={() => ref.click()}>
      Submit
    </button>
  );
}

// The form
const Form = withTheme({});

const schema = {
  title: "Test form",
  type: "string"
};

function MyForm() {
  const { setRef } = useContext(Context);
  return (
      <Form schema={schema}>
        <button ref={setRef} style={{ display: "none" }} />
      </Form>
  );
}

@mzvast
Copy link

mzvast commented Mar 24, 2022

So I was looking for a nice solution here. I could not use new logic implemented here because some other UI team already wrapped react-jsonschema-form in a functional component without exposing its reference point.

So my first idea was to use native HTML5 form control reference.

<JsonForm
  id="json-form"
  onChange={(e) => {
    setForm({ ...form, fdCopy: e.formData });
  }}
  onSubmit={onSubmit}
></JsonForm>;

<Button value="Start" type="submit" form="json-form" />;

This is working fine and there is nothing wrong with this solution but I was looking further to get things done in more reactive fashion. I have noticed that react-jsonschema-form is exposing {children} prop which can be used as a form submitter. Decided to create a reference to that {children} button. Ended up with following solution:

import React, { createRef } from "react";

const submitFormRef = createRef();

<JsonForm
  onChange={(e) => {
    setForm({ ...form, fdCopy: e.formData });
  }}
  onSubmit={onSubmit}
>
  <button ref={submitFormRef} type="submit" style={{ display: "none" }} />>
</JsonForm>;

<Button value="Start" onClick={() => submitFormRef.current.click()} />;

It's working like a charm! Attaching here as maybe someone is looking for a similar thing.

You saved my day! It works like charm!
As other methods I tried including use ref.current.submit method did not work for me.
This is the by now only one method can call submit outside the form.
Thanks a lot !

@zwilderrr
Copy link

Great to hear it!

@stale stale bot closed this as completed Aug 12, 2022
@happywang23
Copy link

So I was looking for a nice solution here. I could not use new logic implemented here because some other UI team already wrapped react-jsonschema-form in a functional component without exposing its reference point.

So my first idea was to use native HTML5 form control reference.

<JsonForm
  id="json-form"
  onChange={(e) => {
    setForm({ ...form, fdCopy: e.formData });
  }}
  onSubmit={onSubmit}
></JsonForm>;

<Button value="Start" type="submit" form="json-form" />;

This is working fine and there is nothing wrong with this solution but I was looking further to get things done in more reactive fashion. I have noticed that react-jsonschema-form is exposing {children} prop which can be used as a form submitter. Decided to create a reference to that {children} button. Ended up with following solution:

import React, { createRef } from "react";

const submitFormRef = createRef();

<JsonForm
  onChange={(e) => {
    setForm({ ...form, fdCopy: e.formData });
  }}
  onSubmit={onSubmit}
>
  <button ref={submitFormRef} type="submit" style={{ display: "none" }} />>
</JsonForm>;

<Button value="Start" onClick={() => submitFormRef.current.click()} />;

It's working like a charm! Attaching here as maybe someone is looking for a similar thing.
@kapalkat @zwilderrr I think the solution above has an issue => the validation won't be trigged at all if you use ref.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
possibly close To confirm if this issue can be closed
Projects
None yet
Development

No branches or pull requests