Form fields lose focus when input value changes

时间:2018-02-01 18:29:42

标签: forms reactjs jsonschema

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.

screen shot 2018-02-01 at 17 50 32

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).

Aside

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?

3 个答案:

答案 0 :(得分:10)

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

schemauiSchemarules作为道具传递给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: []
    };

答案 1 :(得分:0)

对于碰到相同问题但使用Hooks的任何人,这是不使用类的方法:

只需使用在组件外部声明的变量,然后在useEffect内部对其进行初始化。 (别忘了传递[]作为第二个参数,以告诉反应我们不依赖任何变量,复制了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

答案 2 :(得分:-1)

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