react-jsonschema-form および react-jsonschem-form-conditionals を使用して、JSONスキーマから条件付きフィールドを持つフォームを作成しようとしています。
私がレンダリングしているコンポーネントは、FormWithConditionals
とFormModelInspector
です。後者は、フォームモデルを示す非常に単純なコンポーネントです。
関連するソースコードは次のとおりです。
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: []
};
ただし、フィールドの値を変更するたびに、フィールドはフォーカスを失います。 react-jsonschema-form-conditionals
を<FormWithConditionals>
に置き換えても問題が発生しないため、問題の原因は<Form>
ライブラリにあると思われます。
ハンドラーonChange={this.handleFormDataChange}
を削除すると、値が変更されたときに入力フィールドがフォーカスを失うことはなくなりました(ただし、このハンドラーを削除するとFormModelInspector
が壊れます)。
上記のコードで、ハンドラーonChange={this.handleFormDataChange}
を削除すると、フォームデータが変更されても<FormModelInspector>
は更新されません。 <FormModelInspector>
にはformData
属性を介してフォームデータへの参照が渡されるため、このハンドラーが必要な理由がわかりません。おそらく、フォームデータを変更するたびに、同じオブジェクトを変更するのではなく、新しいオブジェクトが作成されるためですか?
問題は非常に単純です。renderメソッドでFormWithConditionals
コンポーネントを作成し、onChange
ハンドラーでsetState
を作成します。これにより、再レンダリングがトリガーされ、 FormWithConditionals
が作成されるため、フォーカスが失われます。このインスタンスは静的な値を使用するため、renderメソッドから、おそらくコンポーネント自体から移動する必要があります。
schema
、uiSchema
、およびrules
が小道具としてConditionalForm
に渡されるため、FormWithConditionals
のインスタンスをconstructor
関数とこのようなレンダリングで使用します
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: []
};
同じ問題にぶつかるがフックを使用している人のために、クラスなしの方法は次のとおりです。
コンポーネントの外部で宣言された変数を使用し、それをuseEffect
の内部で初期化するだけです。 (2番目のパラメーターとして[]
を渡すことを忘れないでください。これは、変数に依存しないことを反応に伝え、componentWillMount
効果を複製します)
// 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
function FormModelInspector
を矢印関数として宣言してみましたか?
const FormModelInspector = props => (
<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>
)