react-jsonschema-form
react-jsonschema-form copied to clipboard
Unable to trigger form submit/validation externally from the form
Prerequisites
- [x] I have read the documentation;
- [x] 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
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.
Related to #155.
@aackerman
Some ideas:
-
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
-
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>
)
};
- 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.
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:
- Calling submit on the HTMLFormElement.
- Create a custom submit event and dispatching it on the form.
- 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.
Thanks for the input @lucaas. I had tried the first two and were unacceptable but I didn't get around to trying number 3.
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.
@spacebaboon sounds good. Would you be willing to create a jsfiddle and contribute it to the Tips & Tricks section?
@n1k0 sure thing :-) might be a few days till I can find time, though.
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} />
}
@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?
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.
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 :)
Well I don't like mine outscopes the submit action, so yours is fine by me. Please send a PR :)
Done :)
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.
That's really nice :smile:
Unfortunately the support from everyone's favourite browser vendor isn't quite there yet http://caniuse.com/#search=form
Will this work for validation as well? Just calling this.validate on the underlying element from the wrapper?
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?
Is this exemple (https://jsfiddle.net/spacebaboon/g5a1re63/) still working ? I cannot import this JSONSchemaForm.default
it works for me just now in Chrome on OSX.
On Tue, 28 Aug 2018 at 08:22, Anthony [email protected] wrote:
Is this exemple (https://jsfiddle.net/spacebaboon/g5a1re63/) still working ? I cannot import this JSONSchemaForm.default
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mozilla-services/react-jsonschema-form/issues/500#issuecomment-416465259, or mute the thread https://github.com/notifications/unsubscribe-auth/AAqG9C4I3A1ZyPEZC7Khu39qOBgqgc-Oks5uVOG5gaJpZM4MPqrr .
@spacebaboon How did you import this line :
const RJSForm = JSONSchemaForm.default;
@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 (
);
}**
@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!
@spacebaboon. Well, If I understood correctly, I followed the way you said like having tabbed display, well in fact not exactly that, but I worked as multiple steps of form each step [some portion of the form, as an array of schemas, ={schema1, schema2, ----;}]each step I made a next button available handle next button outside the form [made default submit: hidden], added submit button reference to next on each page and validating and sending data to collect backend.
thanks
On Wed, Dec 19, 2018 at 9:04 AM Jen Duncan [email protected] wrote:
@spacebaboon https://github.com/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!
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/mozilla-services/react-jsonschema-form/issues/500#issuecomment-448626739, or mute the thread https://github.com/notifications/unsubscribe-auth/Ai2-z3QJLjeK3KI9UqwqwsezMj83PX0yks5u6lWYgaJpZM4MPqrr .
@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?
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.
@StevenVerbiest usually, onSubmit is called only when there are no errors on the form. What exactly do you mean by your question?
@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.
@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.
@StevenVerbiest how are you calling onSubmit programmatically? Can you send a code example?
@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.
Is it possible to call submit from the external button? I would appreciate any example!
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.
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?
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>
);
}
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
reactivefashion. 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 !
Great to hear it!