1

I'm using react and would like an input text that displays a value from an in-memory data store, then updates the store when it's value changes, which triggers a re-render. The issue I'm seeing is that my input text loses focus on the rerender. I've tried setting the key attribute and overriding shouldComponentUpdate and returning false to prevent react from rerendering the component, but to no avail. It seems that the latter may not be a good long-term solution anyway.

From the docs:

Note that in the future React may treat shouldComponentUpdate() as a hint rather than a strict directive, and returning false may still result in a re-rendering of the component.

Code:

export class TextInput extends React.Component {
  constructor(props) {
    super();
    this.storePath = props.value;
    const value = dataStore.get(this.storePath) || '';
    this.state = { value };
  }

  shouldComponentUpdate() { return false; }

  update(event) {
    dataStore.update(this.storePath, event.target.value);
  }

  render() {
    return <input type="text" value={this.state.value} onChange={e => this.update(e)}/>;
  }
}

Edit

I am using react router.

const App = props => (
  <Router history={browserHistory}>
    <div>
      <Switch>{
        Array.from(this.routeMap.keys()).map(path => {
          const viewFn = this.routeMap.get(path);
          return <Route key={path} path={path} render={viewFn}></Route>;
        })
      }</Switch>
    </div>
  </Router>
);

There is a top-level component that is pulled in through the route and that is using my TextInput component.

phil-daniels
  • 574
  • 2
  • 10
  • 28
  • are you using react router? this shouldn't be happening on rerender unless `createElement` is being called – Robbie Milejczak Jan 26 '18 at 15:12
  • Are there any parent components rerendering on update? Does this component perhaps unmount on every update? (validate it with logging in `componentWillUnmount`). – klaasman Jan 26 '18 at 15:13
  • Did you try to set a ref to your input and update the focus on `componentDidUpdate` ? Or use the `autoFocus` ? Or ` input && input.focus()}/>` ? – Striped Jan 26 '18 at 15:13
  • if you are using react router: https://reacttraining.com/react-router/web/api/Route/component if you are not then I would use refs as striped recommends – Robbie Milejczak Jan 26 '18 at 15:16
  • @RobbieMilejczak edited question, yes I'm using react router. – phil-daniels Jan 26 '18 at 15:30
  • @klaasman there is a parent component, yes. Indeed the component does seem to unmount on every update: `componentWillUnmount() { console.log('unmount'); }` – phil-daniels Jan 26 '18 at 15:32
  • @Striped #1 did not work, but it looks like `componentDidUpdate` is not being called `componentDidUpdate() { this.refs.myInput.focus(); console.log("cdu"); }` #2 I can't simply `autofocus` because there may be many of these TextInputs on a page #3 ` input && input.focus()}/>` did refocus the input text, but it set the cursor back to the first position every time. – phil-daniels Jan 26 '18 at 15:34
  • @RobbieMilejczak I am using the render attribute (I just added my router code to the question). – phil-daniels Jan 26 '18 at 15:36
  • okay so ya you need to pass an anonymous function to render, see here: https://stackoverflow.com/a/46335989/8378419 – Robbie Milejczak Jan 26 '18 at 15:41
  • @RobbieMilejczak I already was passing a function, but tried wrapping in an anonymous function and it didn't help `const View = this.routeMap.get(path); return }>;` – phil-daniels Jan 26 '18 at 15:57
  • that's weird .-. you can try giving it a `key` but that's kind of a shot in the dark. – Robbie Milejczak Jan 26 '18 at 16:01

1 Answers1

1

I found it. Everytime I called ReactDOM.render I was redeclaring my top-level component function, which seems to cause my input text to lose focus. It must do an identity check on the passed component function/class to see if it's the same as the previous one and completely rerender from scratch if it's not.

So, here's how NOT to do it:

const render = props => {
  const App = props => (
    <Router history={browserHistory}>
      <Switch>
        <Route key="/login" path="/login" render={() => View(props)}/>
      </Switch>
    </Router>
  );
  ReactDOM.render(
    <App {...props}/>,
    rootDiv
  );
}

render();
render();

Failing jsfiddle here: https://jsfiddle.net/chwsmchd/

Here's the correct way to do it:

const App = props => (
  <Router history={browserHistory}>
    <Switch>
      <Route key="/login" path="/login" render={() => View(props)}/>
    </Switch>
  </Router>
);

const render = props => {
  ReactDOM.render(
    <App {...props}/>,
    rootDiv
  );
}

render();
render();

Correct jsfiddle: https://jsfiddle.net/gzzpmpbz/

phil-daniels
  • 574
  • 2
  • 10
  • 28