admin管理员组

文章数量:1302428

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 {
  ponentDidUpdate(prevProps) {
    if (prevProps.disabled !== this.props.disabled && !this.props.disabled) {
      //  this.ref.focus();  // unment 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"));

I want to focus the input when the checkbox is checked. However, this.ref.focus() must be called only after the ponent re-renders with props.disabled === false, as an input with disabled prop cannot be focused.

If I do the logic in ponentDidUpdate, I'm able to achieve what I want. But this is not a clean solution as the logic is specific to the onClick handler rather than a lifecycle event.

Is there any other way to acplish this? (preferably with a working codesandbox example)

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 {
  ponentDidUpdate(prevProps) {
    if (prevProps.disabled !== this.props.disabled && !this.props.disabled) {
      //  this.ref.focus();  // unment 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"));

I want to focus the input when the checkbox is checked. However, this.ref.focus() must be called only after the ponent re-renders with props.disabled === false, as an input with disabled prop cannot be focused.

If I do the logic in ponentDidUpdate, I'm able to achieve what I want. But this is not a clean solution as the logic is specific to the onClick handler rather than a lifecycle event.

Is there any other way to acplish this? (preferably with a working codesandbox example)

Share Improve this question edited May 26, 2018 at 0:49 Avery235 asked Apr 18, 2018 at 5:43 Avery235Avery235 5,32616 gold badges57 silver badges87 bronze badges 2
  • 1 IMO, update in the ponentDidUpdate is the right way, because re-render and focus is both ponent's state and behavior, you cannot decouple them cleanly. I would even say you should move the toggle state into the ponent and just have some callback props for onToggle and onClick. – Tr1et Commented May 28, 2018 at 3:10
  • 1 @Avery235 what makes you say "this is not a clean solution as the logic is specific to the onClick handler rather than a lifecycle event"? – duhaime Commented Jun 1, 2018 at 19:47
Add a ment  | 

4 Answers 4

Reset to default 2 +25

I think the best thing to do is not rely on refs use state to manage the focus.

This solution instead uses the autoFocus prop on the input, and modifies it when the state of the checkbox changes.

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
  };
  ponentDidUpdate(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"));


I'm not sure why, but changing the autoFocus prop when the ponent was previously disabled doesn't trigger the input to be re-rendered. So I've also added a key to the input to force it.

This is an hypothetical situation and an open issue in REACT(at the same time NOT) since it is consistent with the HTML spec for autofocus (https://developer.mozilla/en-US/docs/Web/HTML/Element/input#Attributes#autofocus). Focus is one of those things that is really tricky to do decoratively because it's part of a shared global state. If 2 unrelated ponents declare that they should be focused in a single render pass, who is right? So REACT give you the hooks to manage that state yourself but it won't do it for you (thus where the work around like the one your are using came).

But It would be great if REACT added the option to focus on render (could be just autoFocusOnRender), and just have the docs warn people of the behavior if multiple things call for focus at once. Ideally this wouldn't happen because an app with good UX would have specific conditions for calling autoFocusOnRender on different inputs.

I would Suggest what you have done is the best way of doing it :). Hope we get an enhancement for this in REACT.

I think that you can have confidence that the updated Redux state data is there before you perform your focus() call, because of the data flow:

  1. Dispatch async action toggleThunk, and wait for its resolution
  2. then dispatch synchronous action to update the state (new state data), and wait for its resolution (?)
  3. then focus() your ref

https://codesandbox.io/s/r57v8r39om

Note that in your OP, your toggle() action creator is not a thunk. Also, it's a good rule to enforce that your thunks return a Promise so that you can control data flow in the way you're describing.

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"));

You can manage this with the prop and a ref. The ref will avoid the need to rerender the input (i.e. for autoFocus to work):

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 {

  ponentDidUpdate(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"));

本文标签: javascriptReduxthunk dispatch an action and wait for rerenderStack Overflow