I'm trying to build a form with conditional fields from a JSON schema using react-jsonschema-form and react-jsonschem-form-conditionals.
The components I'm rendering are a FormWithConditionals
and a FormModelInspector
. The latter is a very simple component that shows the form model.
The relevant source code is:
import React from 'react';
import PropTypes from 'prop-types';
import Engine from "json-rules-engine-simplified";
import Form from "react-jsonschema-form";
import applyRules from "react-jsonschema-form-conditionals";
function FormModelInspector (props) {
return (
<div>
<div className="checkbox">
<label>
<input type="checkbox" onChange={props.onChange} checked={props.showModel}/>
Show Form Model
</label>
</div>
{
props.showModel && <pre>{JSON.stringify(props.formData, null, 2)}</pre>
}
</div>
)
}
class ConditionalForm extends React.Component {
constructor (props) {
super(props);
this.state = {
formData: {},
showModel: true
};
this.handleFormDataChange = this.handleFormDataChange.bind(this);
this.handleShowModelChange = this.handleShowModelChange.bind(this);
}
handleShowModelChange (event) {
this.setState({showModel: event.target.checked});
}
handleFormDataChange ({formData}) {
this.setState({formData});
}
render () {
const schema = {
type: "object",
title: "User form",
properties: {
nameHider: {
type: 'boolean',
title: 'Hide name'
},
name: {
type: 'string',
title: 'Name'
}
}
};
const uiSchema = {};
const rules = [{
conditions: {
nameHider: {is: true}
},
event: {
type: "remove",
params: {
field: "name"
}
}
}];
const FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
return (
<div className="row">
<div className="col-md-6">
<FormWithConditionals schema={schema}
uiSchema={uiSchema}
formData={this.state.formData}
onChange={this.handleFormDataChange}
noHtml5Validate={true}>
</FormWithConditionals>
</div>
<div className="col-md-6">
<FormModelInspector formData={this.state.formData}
showModel={this.state.showModel}
onChange={this.handleShowModelChange}/>
</div>
</div>
);
}
}
ConditionalForm.propTypes = {
schema: PropTypes.object.isRequired,
uiSchema: PropTypes.object.isRequired,
rules: PropTypes.array.isRequired
};
ConditionalForm.defaultProps = {
uiSchema: {},
rules: []
};
However, every time I change a field's value, the field loses focus. I suspect the cause of the problem is something in the react-jsonschema-form-conditionals
library, because if I replace <FormWithConditionals>
with <Form>
, the problem does not occur.
If I remove the handler onChange={this.handleFormDataChange}
the input field no longer loses focus when it's value changes (but removing this handler breaks the FormModelInspector
).
In the code above, if I remove the handler onChange={this.handleFormDataChange}
, the <FormModelInspector>
is not updated when the form data changes. I don't understand why this handler is necessary because the <FormModelInspector>
is passed a reference to the form data via the formData
attribute. Perhaps it's because every change to the form data causes a new object to be constructed, rather than a modification of the same object?
When your onChange event fires, the callback calls setState with the new title value, which gets passed to your text field as a prop. At that point, React renders a new component, which is why you lose focus.
You need to subscribe to the blur event of the textbox and reinstate focus with a small timeout: $('#txtSearch'). blur(function (event) { setTimeout(function () { $("#txtSearch"). focus(); }, 20); });
To set focus on an input field after rendering with React, we can assign a ref to the input element with the useRef hook. Then we call focus on the current value of the ref to focus on the input. to call useRef to create a ref and assign it to inputReference . Then we call inputReference.
To detect if the element has the focus in JavaScript, you can use the read-only property activeElement of the document object. const elem = document. activeElement; The activeElement returns the currently focused element in the document.
The problem is pretty straightforward, you are creating a FormWithConditionals
component in your render method and in your onChange
handler you setState
which triggers a re-render and thus a new instance of FormWithConditionals
is created and hence it loses focus. You need to move this instance out of render method and perhaps out of the component itself since it uses static values.
As schema
, uiSchema
and rules
are passed as props to the ConditionalForm
, you can create an instance of FormWithConditionals
in constructor
function and use it in render like this
import React from 'react';
import PropTypes from 'prop-types';
import Engine from "json-rules-engine-simplified";
import Form from "react-jsonschema-form";
import applyRules from "react-jsonschema-form-conditionals";
function FormModelInspector (props) {
return (
<div>
<div className="checkbox">
<label>
<input type="checkbox" onChange={props.onChange} checked={props.showModel}/>
Show Form Model
</label>
</div>
{
props.showModel && <pre>{JSON.stringify(props.formData, null, 2)}</pre>
}
</div>
)
}
class ConditionalForm extends React.Component {
constructor (props) {
super(props);
this.state = {
formData: {},
showModel: true
};
const { schema, uiSchema, rules } = props;
this.FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
this.handleFormDataChange = this.handleFormDataChange.bind(this);
this.handleShowModelChange = this.handleShowModelChange.bind(this);
}
handleShowModelChange (event) {
this.setState({showModel: event.target.checked});
}
handleFormDataChange ({formData}) {
this.setState({formData});
}
render () {
const FormWithConditionals = this.FormWithConditionals;
return (
<div className="row">
<div className="col-md-6">
<FormWithConditionals schema={schema}
uiSchema={uiSchema}
formData={this.state.formData}
onChange={this.handleFormDataChange}
noHtml5Validate={true}>
</FormWithConditionals>
</div>
<div className="col-md-6">
<FormModelInspector formData={this.state.formData}
showModel={this.state.showModel}
onChange={this.handleShowModelChange}/>
</div>
</div>
);
}
}
ConditionalForm.propTypes = {
schema: PropTypes.object.isRequired,
uiSchema: PropTypes.object.isRequired,
rules: PropTypes.array.isRequired
};
ConditionalForm.defaultProps = {
uiSchema: {},
rules: []
};
For anyone bumping into the same problem but using Hooks, here's how without a class :
Just use a variable declared outside the component and initialize it inside useEffect
. (don't forget to pass []
as second parameter to tell react that we do not depend on any variable, replicating the componentWillMount
effect)
// import ...
import Engine from 'json-rules-engine-simplified'
import Form from 'react-jsonschema-form'
let FormWithConditionals = () => null
const MyComponent = (props) => {
const {
formData,
schema,
uischema,
rules,
} = props;
useEffect(() => {
FormWithConditionals = applyRules(schema, uischema, rules, Engine)(Form)
}, [])
return (
<FormWithConditionals>
<div></div>
</FormWithConditionals>
);
}
export default MyComponent
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With