web-dev-qa-db-ja.com

入力値が変更されると、フォームフィールドはフォーカスを失います

react-jsonschema-form および react-jsonschem-form-conditionals を使用して、JSONスキーマから条件付きフィールドを持つフォームを作成しようとしています。

私がレンダリングしているコンポーネントは、FormWithConditionalsFormModelInspectorです。後者は、フォームモデルを示す非常に単純なコンポーネントです。

screen shot 2018-02-01 at 17 50 32

関連するソースコードは次のとおりです。

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属性を介してフォームデータへの参照が渡されるため、このハンドラーが必要な理由がわかりません。おそらく、フォームデータを変更するたびに、同じオブジェクトを変更するのではなく、新しいオブジェクトが作成されるためですか?

17
Dónal

問題は非常に単純です。renderメソッドでFormWithConditionalsコンポーネントを作成し、onChangeハンドラーでsetStateを作成します。これにより、再レンダリングがトリガーされ、 FormWithConditionalsが作成されるため、フォーカスが失われます。このインスタンスは静的な値を使用するため、renderメソッドから、おそらくコンポーネント自体から移動する必要があります。

schemauiSchema、および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: []
    };
10
Shubham Khatri

同じ問題にぶつかるがフックを使用している人のために、クラスなしの方法は次のとおりです。

コンポーネントの外部で宣言された変数を使用し、それを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
1
Eric Martin

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>
  )
0
Dyo