Принудительный ввод события onChange в родительскую форму со значением, сохраненным в состоянии - PullRequest
4 голосов
/ 12 апреля 2019

РЕДАКТИРОВАТЬ

Извините за неправильный вариант использования.Все входные данные внутри Form проходят через this.props.children, и они могут быть расположены в любой глубокой точке дерева компонентов, поэтому подход передачи handleChange непосредственно к входным данным не будет работать вообще.


Вот фрагмент кода с воспроизведением проблемы.

class CustomSelect extends React.Component {
  items = [
    { id: 1, text: "Kappa 1" },
    { id: 2, text: "Kappa 2" },
    { id: 3, text: "Kappa 3" }
  ]
  
  state = {
    selected: null,
  }
  
  handleSelect = (item) => {
    this.setState({ selected: item })
  }
  
  render() {
    var { selected } = this.state
    return (
      <div className="custom-select">
        <input
          name={this.props.name}
          required
          style={{ display: "none" }} // or type="hidden", whatever
          value={selected
            ? selected.id
            : ""
          }
          onChange={() => {}}
        />
        <div>Selected: {selected ? selected.text : "nothing"}</div>
        {this.items.map(item => {
          return (
            <button 
              key={item.id}
              type="button" 
              onClick={() => this.handleSelect(item)}
            >
              {item.text}
            </button>
          )
        })}
      </div>
    )
  }
}

class Form extends React.Component {
  handleChange = (event) => {
    console.log("Form onChange")
  }
  
  render() {
    return (
      <form onChange={this.handleChange}>
        {this.props.children}
      </form>
    )
  }
}

ReactDOM.render(
  <Form>
    <label>This input will trigger form's onChange event</label>
    <input />
    <CustomSelect name="kappa" />
  </Form>,
  document.getElementById("__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"></div>

Как вы можете видеть, когда вы вводите что-то в ввод по умолчанию (управляемый или неконтролируемый, что угодно), форма ловит всплывающее событие onChange.Но когда вы устанавливаете значение ввода программно (в данном случае state), событие onChange не запускается, поэтому я не могу отловить эти изменения внутри onChange.

Есть ли варианты, чтобы победить эту проблему?Я попытался input.dispatchEvent(new Event("change", { bubbles: true })) сразу после setState({ selected: input }) и внутри его обратного вызова, но результата нет.

Ответы [ 3 ]

3 голосов
/ 15 апреля 2019

Если вы пропустите функцию из формы, вы можете вызвать ее вручную. Вам просто нужно создать new Event(), чтобы соответствовать нужной вам информации. Так как это опора, он будет синхронизироваться, если какие-либо изменения метода произойдут в родительском элементе.

Поскольку вы используете реквизиты для генерации элементов внутри form, вы должны отобразить их следующим образом. Это событие было добавлено только к пользовательским элементам.

class CustomSelect extends React.Component {
  propTypes: {
        onChange: React.PropTypes.func
    }
  items = [
    { id: 1, text: "Kappa 1" },
    { id: 2, text: "Kappa 2" },
    { id: 3, text: "Kappa 3" }
  ]
  
  state = {
    selected: null,
  }
  
  handleSelect = (item) => {
    this.setState({ selected: item });
    this.props.onChange.self(new Event('onchange'))
  };
  
  render() {
    var { selected } = this.state
    return (
      <div className="custom-select">
        <input
          name={this.props.name}
          required
          style={{ display: "none" }} // or type="hidden", whatever
          value={selected
            ? selected.id
            : ""
          }
          onChange={() => {}}
        />
        <div>Selected: {selected ? selected.text : "nothing"}</div>
        {this.items.map(item => {
          return (
            <button 
              key={item.id}
              type="button" 
              onClick={() => this.handleSelect(item)}
            >
              {item.text}
            </button>
          )
        })}
      </div>
    )
  }
}

class Form extends React.Component {
  handleChange = (event) => {
    console.log("Form onChange")
  }
  
  render() {
    let self = this.handleChange;
    let children = React.Children.map(this.props.children, (child, i) => {
          if(typeof child.type === "function"){
            return React.cloneElement(child, {
              onChange: {self}
            });
          }
          return child;
        });
    return (
      <form onChange={this.handleChange}>
        {children}
      </form>
    )
  }
}

ReactDOM.render(
  <Form>
    <label>This input will trigger form's onChange event</label>
    <input />
    <CustomSelect name="kappa" />
  </Form>,
  document.getElementById("__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"></div>
3 голосов
/ 15 апреля 2019

Я действительно думаю, что лучшее, что вы пытаетесь сделать, - это сначала контролировать каждый отдельный вход.Сохраняйте эти значения в состоянии и просто работайте с событием onSubmit из формы.React даже рекомендовал этот подход здесь https://reactjs.org/docs/uncontrolled-components.html

В большинстве случаев мы рекомендуем использовать контролируемые компоненты для реализации форм.В контролируемом компоненте данные формы обрабатываются компонентом React.Альтернативой являются неконтролируемые компоненты, где данные формы обрабатываются самим DOM.

Вы можете прочитать об контролируемых здесь https://reactjs.org/docs/forms.html#controlled-components

Если вы хотите увидеть, как я сделалс простым контролем это будет выглядеть так https://codesandbox.io/s/2w9qnk8lxp Вы можете увидеть, если вы нажмете ввести событие отправки формы со значением keep в состоянии.

class CustomSelect extends React.Component {
  items = [
    { id: 1, text: "Kappa 1" },
    { id: 2, text: "Kappa 2" },
    { id: 3, text: "Kappa 3" }
  ];

  render() {
    return (
      <div className="custom-select">
        <div>
          Selected: {this.props.selected ? this.props.selected.text : "nothing"}
        </div>
        {this.items.map(item => {
          return (
            <button
              key={item.id}
              type="button"
              onClick={() => this.props.onChange(item)}
            >
              {item.text}
            </button>
          );
        })}
      </div>
    );
  }
}

class Form extends React.Component {
  state = {
    firstInput: "",
    selected: null
  };

  handleSubmit = event => {
    event.preventDefault();
    console.log("Form submit", this.state);
  };

  handleInputChange = name => event => {
    this.setState({ [name]: event.target.value });
  };

  handleSelectedChanged = selected => {
    this.setState({ selected });
  };

  render() {
    console.log(this.state);
    return (
      <form onSubmit={this.handleSubmit}>
        <label>This input will trigger form's onChange event</label>
        <input
          value={this.state.firstInput}
          onChange={this.handleInputChange("firstInput")}
        />
        <CustomSelect
          name="kappa"
          selected={this.state.selected}
          onChange={this.handleSelectedChanged}
        />
      </form>
    );
  }
}

Но если вы действительно хотите, чтобы ваш путьВы должны передать функцию handleChange в качестве обратного вызова дочерним элементам и использовать этот реквизит в качестве функции при нажатии на элемент.Пример здесь https://codesandbox.io/s/0o8545mn1p.

class CustomSelect extends React.Component {
  items = [
    { id: 1, text: "Kappa 1" },
    { id: 2, text: "Kappa 2" },
    { id: 3, text: "Kappa 3" }
  ];

  state = {
    selected: null
  };

  handleSelect = item => {
    this.setState({ selected: item });

    this.props.onChange({ selected: item });
  };

  render() {
    var { selected } = this.state;
    return (
      <div className="custom-select">
        <input
          name={this.props.name}
          required
          style={{ display: "none" }} // or type="hidden", whatever
          value={selected ? selected.id : ""}
          onChange={() => {}}
        />
        <div>Selected: {selected ? selected.text : "nothing"}</div>
        {this.items.map(item => {
          return (
            <button
              key={item.id}
              type="button"
              onClick={() => this.handleSelect(item)}
            >
              {item.text}
            </button>
          );
        })}
      </div>
    );
  }
}

class Form extends React.Component {
  handleChange = event => {
    console.log("Form onChange");
  };

  render() {
    return (
      <form onChange={this.handleChange}>
        <label>This input will trigger form's onChange event</label>
        <input />
        <CustomSelect name="kappa" onChange={this.handleChange} />
      </form>
    );
  }
}
1 голос
/ 22 апреля 2019

Обновите ваш CustomSelect компонент следующим:

class CustomSelect extends React.Component {

    ...

    // you'll use this reference to access the html input.
    ref = React.createRef();

    handleSelect = item => {
        this.setState({ selected: item });

        // React overrides input value setter, but you can call the
        // function directly on the input as context
        const inputValSetter = Object.getOwnPropertyDescriptor(
            window.HTMLInputElement.prototype,
            "value"
        ).set;
        inputValSetter.call(this.ref.current, "dummy");

        // fire event
        const ev = new Event("input", { bubbles: true });
        this.ref.current.dispatchEvent(ev);
    };

    ...

    render() {
        ...

        return (
            <div className="custom-select">
                <input
                    // you'll use the reference in `handleSelect`
                    ref={this.ref}
                    name={this.props.name}
                    required
                    style={{ display: "none" }} // or type="hidden", whatever
                    value={selected ? selected.id : ""}
                    onChange={() => {}}
                />

                ...

            </div>
        );
    }

    ...
}

И ваш Form компонент следующим:

class Form extends React.Component {

    handleChange = event => {
        console.log("Form onChange");

        // remove synthetic event from pool
        event.persist();
    };

    ...
}
...