setState условно реагирует - PullRequest
0 голосов
/ 16 октября 2018

Я знаю, что некоторые реагируют, но я застрял в странной ситуации.

У меня есть два входа и кнопка, кнопка должна быть включена, когда оба входа не пусты.Итак, я использовал свойство состояния для каждого входного значения, а также одно свойство, сообщающее мне, имеют ли оба входа значение:

this.state = {
  title: '',
  time :'', 
  enabled : false
}

также у меня есть onChange для каждого входа, чтобы соответственно установить State:

<input type="text" id="time" name="time" onChange={this.onChange.bind(this)} value={this.state.time}></input>
<input type="text" id="title" name="title" onChange={this.onChange.bind(this)} value={this.state.title}></input>

и onChange выглядит следующим образом

onChange(e){
    this.setState({
        [e.target.id] : e.target.value,
        enabled : ( (this.state.title==='' || this.state.time==='' ) ? false : true)
      });
  }

проблема в том, что setState просматривает предыдущее состояние, а включенный всегда на один шаг позади, поэтому, если я наберу X первым, а Y вторым,все еще включенный будет ложным.

Мне удалось решить эту проблему, используя setTimeout и заняв в нем вторую строку, но мне это показалось неправильным.

  onChange(e){

    this.setState({
        [e.target.id] : e.target.value,
    });

    setTimeout(() => {
      this.setState({
        enabled : ( (this.state.title==='' || this.state.time==='' ) ? false : true)
    });
    }, 0);

  }

Есть ли лучшие решения?

Ответы [ 4 ]

0 голосов
/ 16 октября 2018

@ Шубхам Хатри имеет правильный ответ (вам действительно не нужно свойство enabled в штате), но важно понимать, что происходит с государством и почему ваше hack использование setTimeout сработало(к счастью, на мой взгляд).

Из официальных документов

setState () не всегда сразу обновляет компонент.Это может пакетировать или отложить обновление до позже.Это делает чтение this.state сразу после вызова setState () потенциальной ловушкой.Вместо этого используйте componentDidUpdate или обратный вызов setState (setState (Updater, callback)), который гарантированно сработает после применения обновления.

Кроме того, обновления состояния могут быть асинхронными:

React может объединять несколько вызовов setState () в одно обновление для повышения производительности.

Поскольку this.props и this.state могут обновляться асинхронно, вам не следуетполагаться на их значения для расчета следующего состояния.

Надеюсь, это прояснит вашу проблему.

0 голосов
/ 16 октября 2018

Если вы хотите установить состояние после того, как произошли текущие изменения, вы можете передать второй параметр (обратный вызов) в setState, который произойдет после того, как состояние уже установлено, и снова установите ваше состояние там.Это вызовет дополнительный рендеринг, но, похоже, это то, что вы просите.Это просто для того, чтобы вы знали, как что-то сделать после того, как произошли текущие изменения состояния ...

Но , я бы пошел с ответом Шубхам Хатри.Это было бы гораздо более логичным.

0 голосов
/ 16 октября 2018

Есть ли лучшие решения?

Для этого есть 3 решения: просто выберите одно


Решение 1. Использование componentDidUpdate()

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      time: "",
      enabled: false
    };
  }
  onChange(e) {
    this.setState({
      [e.target.id]: e.target.value
    });
  }
   componentDidUpdate(prevProps, prevState) {
     if (
       prevState.time !== this.state.time ||
       prevState.title !== this.state.title
     ) {
       if (this.state.title && this.state.time) {
         this.setState({ enabled: true });
       } else {
         this.setState({ enabled: false });
       }
     }
   }
  render() {
    return (
      <React.Fragment>
        <input
          type="text"
          id="time"
          name="time"
          onChange={this.onChange.bind(this)}
          value={this.state.time}
        />
        <input
          type="text"
          id="title"
          name="title"
          onChange={this.onChange.bind(this)}
          value={this.state.title}
        />
        <button disabled={!this.state.enabled}>Button</button>
      </React.Fragment>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

Решение 2: Использование обратного вызова setState

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      time: "",
      enabled: false
    };
  }
  onChange(e) {
     this.setState({[e.target.id]: e.target.value},
       () => {
         if (this.state.title && this.state.time) {
           this.setState({ enabled: true });
         } else {
           this.setState({ enabled: false });
         }
       }
     );
  }
  render() {
    return (
      <React.Fragment>
        <input
          type="text"
          id="time"
          name="time"
          onChange={this.onChange.bind(this)}
          value={this.state.time}
        />
        <input
          type="text"
          id="title"
          name="title"
          onChange={this.onChange.bind(this)}
          value={this.state.title}
        />
        <button disabled={!this.state.enabled}>Button</button>
      </React.Fragment>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

Решение 3: Расчет enabled на основе title и time

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      time: ""
    };
  }
  onChange(e) {
    this.setState({
      [e.target.id]: e.target.value
    });
  }
  render() {
    const enabled = this.state.time && this.state.title;
    return (
      <React.Fragment>
        <input
          type="text"
          id="time"
          name="time"
          onChange={this.onChange.bind(this)}
          value={this.state.time}
        />
        <input
          type="text"
          id="title"
          name="title"
          onChange={this.onChange.bind(this)}
          value={this.state.title}
        />
        <button disabled={!enabled}>Button</button>
      </React.Fragment>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
0 голосов
/ 16 октября 2018

Прежде всего, вам не нужно поддерживать состояние для включенного, поскольку оно может быть получено из других значений состояния и может быть напрямую выполнено при рендеринге

onChange(e){
    this.setState({
        [e.target.id] : e.target.value,
    });
}
render() {
   const enabled = this.state.title !== "" && this.state.time !== "";
   return (
       <div>
          <input type="text" id="time" name="time" onChange={this.onChange.bind(this)} value={this.state.time}></input>
          <input type="text" id="title" name="title" onChange={this.onChange.bind(this)} value={this.state.title}></input>
          <button disabled={!enabled}>Button</button>
       </div>
   )
}
...