首页 > 解决方案 > 反应表单 - 值更改时自动取消选择字段

问题描述

我是 React 的初学者,正在尝试构建我的第一个表单。

我使用 API 获取在所述表单中提出的问题,一旦提交表单,我想通过相同的 API 创建一个答案对象。

问题是一切似乎都很好(问题呈现正常,答案对象在状态下更新),但是每次字段的值发生变化时,我都必须重新选择该字段才能继续输入。基本上,就像我在值更改时自动点击离开该字段一样。

这是发生的事情:

在此处输入图像描述

这是(可能)有问题的代码片段:

class Form extends React.Component {
    constructor(props) {
        super(props);
        {/* Questions are filled on page load, answers is what I'm working with */}
        this.state = { questions: [], answers: [] }; 
        this.handleChange = this.handleChange.bind(this);
    }


    // This is where the magic is supposed to happen
    handleChange(event) {
        let key = event.target.id,
            value = event.target.value;

        {/* Here, my goal is to build an object with questions ids as keys and values of fields as key values. */}

        this.setState(prevState => {
            let copy = Object.assign({}, prevState.answers);
            copy[key] = value;
            return {  answers: copy };
        })

    }

    render() {
        const { questions } = this.state;

        const TextareaInput = (fieldId) => (
            <div>
                <textarea name={ fieldId.fieldId } value={this.state.answers[fieldId.fieldId]} id={ fieldId.fieldId } onChange={this.handleChange}  ></textarea>
            </div>
        );

        const TextInput = (fieldId) =>(
            <div>
                <input type='text' name={ fieldId.fieldId } value={this.state.answers[fieldId.fieldId]} id={ fieldId.fieldId } onChange={this.handleChange} />
            </div>
        );

        const allQuestions =  (
            questions.map((q, key) =>
                <div key={q.id} className='question'>
                    <label htmlFor={ q.field_id } className={ q.required ? 'required' : '' } dangerouslySetInnerHTML={{__html: q.question}}></label>

                    {q.field_type == 'text' ? <TextInput fieldId={q.field_id}/> : <TextareaInput fieldId={q.field_id}/>}
                </div>
            )
        )

        return (
            <form>
                { allQuestions }
            </form>
        )
    }

}
export default Form;

pastebin 上的完整组件

我认为问题来自我的 handleChange 函数,但我不确定是什么原因造成的。我尝试添加一些东西并在没有任何运气的情况下移动一些东西......

标签: javascriptreactjs

解决方案


您需要调用TextInputTextareaInput类似的函数,而不是像单独的组件一样使用它们,因为您在组件中定义了它们。

    {q.field_type == 'text'
      ? TextInput(q.field_id)
      : TextareaInput(q.field_id)}

React 无法保持对它们的直接引用,并且似乎在每次渲染时都将它们视为不同的元素。

另外,我相信您已经知道,dangerouslySetInnerHTML顾名思义,您应该小心使用,这可能很危险。

class Form extends React.Component {
  constructor(props) {
    super(props);
    {
      /* Questions are filled on page load, answers is what I'm working with */
    }
    this.state = {
      questions: [
        {
          id: 2,
          field_id: 2,
          question: 'How are you today?',
          field_type: 'text',
        },
                {
          id: 3,
          field_id: 3,
          question: 'What\'s the answer to life, the universe, and everything??',
          field_type: 'textarea',
        },
      ],
      answers: [],
    };
    this.handleChange = this.handleChange.bind(this);
  }

  // This is where the magic is supposed to happen
  handleChange(event) {
    let key = event.target.id,
      value = event.target.value;

    {
      /* Here, my goal is to build an object with questions ids as keys and values of fields as key values. */
    }

    this.setState((prevState) => {
      let copy = Object.assign({}, prevState.answers);
      copy[key] = value;
      return { answers: copy };
    },()=>{console.log(this.state)});
  }

  render() {
    const { questions } = this.state;

    const TextareaInput = (fieldId) => (
      <div>
        <textarea
          name={fieldId}
          value={this.state.answers[fieldId]}
          id={fieldId}
          onChange={this.handleChange}
        ></textarea>
      </div>
    );

    const TextInput = (fieldId) => (
      <div>
        <input
          type="text"
          name={fieldId}
          value={this.state.answers[fieldId]}
          id={fieldId}
          onChange={this.handleChange}
        />
      </div>
    );

    const allQuestions = questions.map((q, key) => (
      <div key={q.id} className="question">
        <label
          htmlFor={q.field_id}
          className={q.required ? 'required' : ''}
          // As I'm sure you are already aware, this is likely a terrible idea.
          dangerouslySetInnerHTML={{ __html: q.question }}
        ></label>
        {q.field_type == 'text'
          ? TextInput(q.field_id)
          : TextareaInput(q.field_id)}
      </div>
    ));

    return <form>{allQuestions}</form>;
  }
}

ReactDOM.render(<Form/>, document.querySelector('#root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root" />


为了避免使用dangerouslySetInnerHTML问题,我建议使用某种降​​价渲染器。对于大多数提问的用例来说,这应该足够好了。

https://www.npmjs.com/package/react-markdown

https://www.markdownguide.org/

class Form extends React.Component {
  constructor(props) {
    super(props);
    {
      /* Questions are filled on page load, answers is what I'm working with */
    }
    this.state = {
      questions: [
        {
          id: 2,
          field_id: 2,
          question: 'How are *you* today?',
          field_type: 'text',
        },
                {
          id: 3,
          field_id: 3,
          question: 'What\'s the **answer** to life, the universe, and everything??',
          field_type: 'textarea',
        },
        {id: 4,
        field_id: 4,
        field_type: 'text',
        question:`# This is the big question
#### *ARE YOU READY?*
1. Is this the real life?
1. Or is this just fantasy?
`
        }
      ],
      answers: [],
    };
    this.handleChange = this.handleChange.bind(this);
  }

  // This is where the magic is supposed to happen
  handleChange(event) {
    let key = event.target.id,
      value = event.target.value;

    {
      /* Here, my goal is to build an object with questions ids as keys and values of fields as key values. */
    }

    this.setState((prevState) => {
      let copy = Object.assign({}, prevState.answers);
      copy[key] = value;
      return { answers: copy };
    },()=>{console.log(this.state)});
  }

  render() {
    const { questions } = this.state;

    const TextareaInput = (fieldId) => (
      <div>
        <textarea
          name={fieldId}
          value={this.state.answers[fieldId]}
          id={fieldId}
          onChange={this.handleChange}
        ></textarea>
      </div>
    );

    const TextInput = (fieldId) => (
      <div>
        <input
          type="text"
          name={fieldId}
          value={this.state.answers[fieldId]}
          id={fieldId}
          onChange={this.handleChange}
        />
      </div>
    );

    const allQuestions = questions.map((q, key) => (
      <div key={q.id} className="question">
        <label
          htmlFor={q.field_id}
          className={q.required ? 'required' : ''}
        ><ReactMarkdown source={q.question}/></label>
        {q.field_type == 'text'
          ? TextInput(q.field_id)
          : TextareaInput(q.field_id)}
      </div>
    ));

    return <form>{allQuestions}</form>;
  }
}

ReactDOM.render(<Form/>, document.querySelector('#root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-markdown/4.3.1/react-markdown.js" integrity="sha256-4jDgUokdWbazrdnMjWm+TftvBFnOwSNIpvKhgYsInfw=" crossorigin="anonymous"></script>

<div id="root" />


如果您需要完整的渲染功能:

https://pragmaticwebsecurity.com/files/cheatsheets/reactxss.pdf给出了一个使用DOMPurify来清理输入以防止跨站点脚本和其他在 html 之外呈现的危险行为的示例。

因此,为此,您将净化字符串,然后在净化后将其传递给它dangerouslySetInnerHTML


推荐阅读