输入值更改时表单字段失去焦点

Form fields lose focus when input value changes

我正在尝试使用 react-jsonschema-form and react-jsonschem-form-conditionals.

从 JSON 模式构建一个包含条件字段的表单

我正在渲染的组件是 FormWithConditionalsFormModelInspector。后者是一个非常简单的显示表单模型的组件。

相关源码为:

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 库中的东西,因为如果我用 <Form> 替换 <FormWithConditionals>,问题就不会发生。

如果我删除处理程序 onChange={this.handleFormDataChange} 输入字段在其值更改时不再失去焦点(但删除此处理程序会破坏 FormModelInspector)。

放在一边

在上面的代码中,如果我删除处理程序 onChange={this.handleFormDataChange},当表单数据更改时 <FormModelInspector> 不会更新。我不明白为什么需要这个处理程序,因为 <FormModelInspector> 通过 formData 属性传递了对表单数据的引用。也许是因为表单数据的每一次改变都会导致构造一个新的对象,而不是对同一个对象的修改?

问题非常简单,您在渲染方法中创建了一个 FormWithConditionals 组件,在 onChange 处理程序中您 setState 触发了重新渲染,从而触发了一个新的FormWithConditionals 的实例已创建,因此它失去了焦点。您需要将此实例移出 render 方法,或许还移出组件本身,因为它使用静态值。

由于 schemauiSchemarules 作为属性传递给 ConditionalForm,您可以在 [=20] 中创建 FormWithConditionals 的实例=] 函数并像这样在渲染中使用它

    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: []
    };

您是否尝试过将 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>
  )

对于遇到相同问题但使用 Hooks 的任何人,这里是没有 class 的方法:

只需使用在组件外部声明的变量并在内部初始化它 useEffect。 (不要忘记传递 [] 作为第二个参数来告诉 React 我们不依赖于任何变量,复制 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