Redux-thunk отправляет действие и ждет повторной обработки

import React from "react";
import { render } from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider, connect } from "react-redux";
import thunk from "redux-thunk";

const disabled = (state = true, action) => {
  return action.type === "TOGGLE" ? !state : state;
};

class Button extends React.Component {
  componentDidUpdate(prevProps) {
    if (prevProps.disabled !== this.props.disabled && !this.props.disabled) {
      //  this.ref.focus();  // uncomment this to see the desired effect
    }
  }
  render() {
    const { props } = this;
    console.log("rendering", props.value);
    return (
      <div>
        <input
          type="checkbox"
          onClick={() => {
            props.toggle();
            this.ref.focus(); // doesn't work
          }}
        />
        <input
          disabled={props.disabled}
          ref={ref => {
            this.ref = ref;
          }}
        />
      </div>
    );
  }
}

const toggle = () => ({
  type: "TOGGLE"
});

const A = connect(state => ({ disabled: state }), { toggle })(Button);

const App = () => (
  <Provider store={createStore(disabled, applyMiddleware(thunk))}>
    <A />
  </Provider>
);

render(<App />, document.getElementById("root"));

Edit redux-thunk-promise

Я хочу сфокусировать input когда флажок установлен. Однако this.ref.focus() необходимо вызывать только после повторного рендеринга компонента с помощью props.disabled === false, поскольку input с disabled поддержкой не может быть сфокусирован.

Если я сделаю логику в componentDidUpdate, я смогу добиться того, чего хочу. Но это не чистое решение, так как логика специфична для обработчика onClick а не для события жизненного цикла.

Есть ли другой способ сделать это? (предпочтительно с примером рабочих кодов и ящиков)

Ответ 1

Я думаю, лучшее, что нужно сделать, это не полагаться на состояние использования refs для управления фокусом.

Это решение вместо этого использует autoFocus на входе и изменяет его при изменении состояния флажка.

import React from "react";
import { render } from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider, connect } from "react-redux";
import thunk from "redux-thunk";

const disabled = (state = true, action) => {
  return action.type === "TOGGLE" ? !state : state;
};

class Button extends React.Component {
  state = {
    checked: false,
    focus: false
  };
  componentDidUpdate(prevProps, prevState) {
    if (prevState.checked !== this.state.checked) {
      this.props.toggle();
      this.setState({
        focus: this.state.checked
      });
    }
  }
  render() {
    const { props } = this;
    const { checked, focus } = this.state;
    console.log("rendering", props.value, checked);
    return (
      <div>
        <input
          type="checkbox"
          checked={checked}
          onClick={({ target }) => {
            this.setState({ checked: target.checked });
          }}
        />
        <input key={'input_${checked}'} autoFocus={focus} />
      </div>
    );
  }
}

const toggle = () => ({
  type: "TOGGLE"
});

const A = connect(state => ({ disabled: state }), { toggle })(Button);

const App = () => (
  <Provider store={createStore(disabled, applyMiddleware(thunk))}>
    <A />
  </Provider>
);

render(<App />, document.getElementById("root"));

Edit redux-thunk-promise


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

Ответ 2

Это гипотетическая ситуация и открытая проблема в REACT (в то же время NOT), поскольку она согласуется с спецификацией HTML для автофокуса (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input # Атрибуты # автофокусировка). Фокус - одна из тех вещей, которые действительно сложно сделать декоративно, потому что они являются частью общего глобального состояния. Если 2 несвязанных компонента заявляют, что они должны быть сфокусированы в одном проходе визуализации, кто прав? Таким образом, REACT дает вам крючки для управления этим состоянием, но он не будет делать этого для вас (таким образом, когда работает работа, подобная той, которую вы используете).

Но было бы здорово, если бы РЕАКТ добавил возможность сосредоточиться на рендеринге (может быть просто autoFocusOnRender), и просто чтобы документы были предупреждены людям о поведении, если несколько вещей требуют фокуса сразу. В идеале этого не произойдет, потому что приложение с хорошим UX будет иметь особые условия для вызова autoFocusOnRender на разных входах.

Я бы предложил, что вы сделали, это лучший способ сделать это :). Надеюсь, мы получим повышение для этого в REACT.

Ответ 3

Я думаю, что вы можете быть уверены, что обновленные данные state Redux находятся перед выполнением вызова focus() из-за потока данных:

  1. Отправлять асинхронное действие toggleThunk и ждать его разрешения
  2. then отправьте синхронное действие для обновления state (новые данные state) и дождитесь его разрешения (?)
  3. then focus() ваш ref

https://codesandbox.io/s/r57v8r39om

Edit redux-thunk-promise

Обратите внимание, что в вашем OP ваш создатель действия toggle() не является thunk. Кроме того, это хорошее правило для обеспечения того, чтобы ваши thunks возвращали Promise, чтобы вы могли контролировать поток данных в том виде, в котором вы описываете.

import React from "react";
import { render } from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider, connect } from "react-redux";
import thunk from "redux-thunk";

const disabled = (state = true, action) => {
  return action.type === "TOGGLE" ? !state : state;
};

class Button extends React.Component {
  textInput = React.createRef();

  handleClick = () => {
    const { toggleThunk } = this.props;
    toggleThunk().then(() => {
      this.textInput.current.focus();
    });
  };

  render() {
    const { disabled, value } = this.props;
    return (
      <div>
        <input type="checkbox" onClick={this.handleClick} />
        <input disabled={disabled} ref={this.textInput} />
      </div>
    );
  }
}

// Action
const toggle = () => ({
  type: "TOGGLE"
});

// Thunk
const toggleThunk = () => dispatch => {
  // Do your async call().then...
  return Promise.resolve().then(() => dispatch(toggle()));
};

const A = connect(state => ({ disabled: state }), { toggleThunk })(Button);

const App = () => (
  <Provider store={createStore(disabled, applyMiddleware(thunk))}>
    <A />
  </Provider>
);

render(<App />, document.getElementById("root"));

Ответ 4

Вы можете управлять этим с помощью prop и ref. Ссылка будет избегать необходимости повторного ввода ввода (т. autoFocus Для работы autoFocus):

import React, { Component } from "react";
import { render } from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider, connect } from "react-redux";
import thunk from "redux-thunk";

const disabled = (state = true, action) => {
  return action.type === "TOGGLE" ? !state : state;
};

class Button extends Component {

  componentDidUpdate(prevProps) {
    if (!this.props.disabled && prevProps.disabled) {
      this.ref.focus();
    }
  }

  render() {
    const { disabled } = this.props;
    return (
      <div>
        <input
          type="checkbox"
          checked={!disabled}
          onClick={() => {
            this.props.toggle();
          }}
        />
        <input
          disabled={disabled}
          ref={ref => {
            this.ref = ref;
          }}
        />
      </div>
    );
  }
}

const toggle = () => ({
  type: "TOGGLE"
});

const A = connect(state => ({ disabled: state }), { toggle })(Button);

const App = () => (
  <Provider store={createStore(disabled, applyMiddleware(thunk))}>
    <A />
  </Provider>
);

render(<App />, document.getElementById("root"));

Edit redux-thunk-promise