Material-ui: [TextField] loses focus on rerender

Created on 8 Jun 2015  ·  41Comments  ·  Source: mui-org/material-ui

If a textfield that had focus, is rerendered by setState, it looses focus.

question v0.x

Most helpful comment

@scotmatson I can confirm that, at least in my case, the issue is resolved if I turn the Container = styled.div that I have as a wrapper into a normal, non-styled-component div.

Edit:
And I also share your frustrations. It would be really nice if these two libraries worked together flawlessly. This issue and the specificity issue are two things that I hope get ironed out soon but I am grateful for the people volunteering their time.

Edit2:
I Just discovered what was causing the problem in my case: declaring styled-component components in the render method! This was causing the components to be recreated on every re-render and React couldn't track which element had focus across the re-render boundary. This was also leading to a few other re-render issues for me.

Moving all my styled-component declarations to outside of my component resolved the losing focus issues for me. Probably a noob mistake but no one has mentioned that here so I thought I would come back and report.

All 41 comments

+1

I have to use the onBlur event with defaultValue to get the Textfields working smoothly.

anyone have a workaround to make this work?

I don't think this an issue any more. See code below:

//taken from examples/webpack-example

/** In this file, we create a React component which incorporates components provided by material-ui */

const React = require('react');
const RaisedButton = require('material-ui/lib/raised-button');
const TextField = require('material-ui/lib/text-field');
const Dialog = require('material-ui/lib/dialog');
const ThemeManager = require('material-ui/lib/styles/theme-manager');
const LightRawTheme = require('material-ui/lib/styles/raw-themes/light-raw-theme');
const Colors = require('material-ui/lib/styles/colors');

const Main = React.createClass({

  childContextTypes: {
    muiTheme: React.PropTypes.object,
  },

  getInitialState () {
    return {
      muiTheme: ThemeManager.getMuiTheme(LightRawTheme),
      counter: 0,
    };
  },

  getChildContext() {
    return {
      muiTheme: this.state.muiTheme,
    };
  },

  componentWillMount() {
    let newMuiTheme = ThemeManager.modifyRawThemePalette(this.state.muiTheme, {
      accent1Color: Colors.deepOrange500,
    });

    this.setState({muiTheme: newMuiTheme});
  },

  componentDidMount() {
    this.refs.textField.focus();
  },

  render() {

    console.log(this.state.counter);

    let containerStyle = {
      textAlign: 'center',
      paddingTop: '200px',
    };

    let standardActions = [
      { text: 'Okay' },
    ];

    return (
      <div style={containerStyle}>
        <Dialog
          title="Super Secret Password"
          actions={standardActions}
          ref="superSecretPasswordDialog">
          1-2-3-4-5
        </Dialog>

        <h1>material-ui</h1>
        <h2>example project</h2>

        <TextField ref="textField" onChange = {this._incrementStateCounter}/>
        <RaisedButton label="Super Secret Password" primary={true} onTouchTap={this._handleTouchTap} />

      </div>
    );
  },

  _handleTouchTap() {
    this.refs.superSecretPasswordDialog.show();
  },

  _incrementStateCounter () {
    this.setState({counter: this.state.counter + 1});
  },

});

module.exports = Main;


Here's the app in action (with updated state being logged in the console as the TextField input changes). You can see that the TextField retains focus:

4uo63bzhmj

I still get this. @shaurya947 example didn't reproduced the bug since the TextField didn't have an initial value property.
to reproduce use
<TextField ref="textField" value={this.state.valueToBeEdited} onChange = {this._incrementStateCounter}/>

or

<TextField ref="textField" defaultValue={this.state.valueToBeEdited} onChange = {this._incrementStateCounter}/>

I am also seeing what appears to be a regression of this issue. My TextFields are losing focus on re-render. I'm setting the value prop of the TextField, so it is a controlled component.

I do notice a new warning in the console: 'TextField is changing an uncontrolled input of type text to be controlled'. I don't recall seeing that before, not sure if it's relevant here or not. The warning appears once I enter the first character into the TextField.

Through a little debugging, it appears that something is causing the TextField to lose focus. The internal onBlur handler is invoked, which toggles the isFocused internal state of the TextField to false. If I log document.activeElement during the onBlur handler (to determine what component has stole focus) then it logs the root of the document (body).

I was able to narrow this down further by determining that the source of the blur event was the first MenuItem within a Menu elsewhere in the app. Setting the disableAutoFocus property to true resolved my issue. Hope this helps someone else.

I've opened a separate issue to capture this: #4387.

I can confirm this is still an issue...

Seeing it in a very simple login form - I'm using this in conjunction with redux forms and when I interact with the form it causes a re-render (touched a field in redux-form parlance) which loses the focus.

As a workaround, I'm only re-rendering the actual form if there been a change in the error state.

You can reproduce this issue easily by putting the TextField within a Table set as selectable=true. Whenever you click on a text field to start editing it, the line will get the focus, thus change its background color (I guess some props of the line might be set as selected=true), thus trigger a re-render, thus you'll lose the focus...

So you basically can't use a TextField within a selectable Table, not sure it's a good UX practice anyway, but still :)

We noticed that on our project, I can try to push a reproducer.

Any work around to this? Also noticing this when using a regular input text area inside of a table.

@dalexander01 Found a "solution", but it seems very redux-form specific as was experiencing the same problem when not using md input components. If its of any use I can post a snippet!

@deyceg please do, it cant hurt 😄 . Just run into this issue with this combination of a <Dialog/> and a <TextField/>.

      <Dialog
        open={this.props.showDialog}
        title={'New ' + this.props.type}
        autoScrollBodyContent={true}
        actions={actions}
        bodyStyle={styles.dialogContent}
      >
        <SelectField
          name='sub_type'
          value={this.props.top_card.sub_type}
          onChange={(e, k, payload) => this.props.topCardSelectChange('sub_type', payload)}
          floatingLabelText='Type'
        >
          {menuItemsJSX}
        </SelectField>
        <TextField
          name='title'
          className='story-title'
          value={this.props.top_card.title}
          onChange={this.props.topCardChange}
          floatingLabelText='Title'
          fullWidth={true}
          multiLine={true}
          style={styles.title}
        />
        <TextField />
      </Dialog>

I solved it for my case, you need to provide an unique id to that text field.
...
The id should not change after re-render though. This way React can keep track of which element was focused or not.
P.S. I had textfields in an array which is rendered via myArray.map, there you have to provide same keys when rerender.

EDIT: I tested again, just having the "key" on myArray to be the same across rerenders solved the issue.
The textfield id changed to shortid.generate().

    params.map(function(p,i){
        return(
           <div key={i}>              <--- this will always be the same for this particular row.
               <div className='param-inner'>
                   <TextField id={shortid.generate()} value = {p.value} onChange={this.updateParam.bind(this,i)}/>               <-- id here can be random, but it is necessary to avoid browser warning "we don't have enough information..."
                   </div>
               <div className='param-inner'>{p.unit}</div>
           </div>
        )
    }.bind(this));

Anyone managed to find a workaround this issue (without using refs)? It seems to be the combination of onChange and setting the value prop to a state key that makes the input loose focus.

I had the following issue too, I solved it though by having taking the SelectField implementation if you see it internally is calling a DropdownMenu https://github.com/callemall/material-ui/blob/ccf712c5733508784cd709c18c29059542d6aad1/src/SelectField/SelectField.js#L192

so I also need it to add a DropDownMenu this internally is wrapping the components in a Menu, I added disableAutoFocus to make my TextField keep the focus on it as show bellow.

Maybe we can expose a DropDownMenu component from the SelectField and allow autofocus to be disable with props.

out

One workaround for this for those who are struggling with it and just want something to work, though its possible its likely not applicable in some situations and could present issues (I am not entirely certain myself though) is to use a variable on the instance rather than the state. So instead of this.state.value it would be this.value that onChange would be updating. The component would not have a value prop. Then on the onChange handler you would use e.target.value.

The only difference I know of between a variable on the instance vs. the state is that the instance var will not trigger a re-render. Though there could be other reasons so I'm hoping other's will catch my slack on this one.

Still getting this error. Any work arounds?

The workaround I found was to override the shouldComponentUpdate function to my component and then return false when I changed the state values used by my textfields.

`shouldComponentUpdate(nextProps, nextState) {    

    if(nextState.email != this.state.email) {  

      return false;  

    }

    else if(nextState.password != this.state.password) {  

      return false;  

    }  
    else return true;  
    }

`

Doing it this way fixed my issue. The issue seems to be the component rendering every time the state changes which then resets focus. So by using the above function to disable rendering when you do an onChange event to set state values the component doesn't reload.

@Aspintyl000 this works... somewhat. When I am setting the value of the textfield through the 'value' prop, and I am setting that prop to 'this.state.value', nothing shows up. Although, when I remove that, it seems to work.

i am using textfields in a table row, and this is a problem :( any progress?

Psid.txt

I was also getting the same error . But after using componentDidMount() {
this.refs.textField.focus();
}
it does not lose focus. But it does not show the input value.

I tried to add <TextField /> in a <Table />. Like the above situations, I cannot focus the input.
My solution: add a onClick event to the , when I click it, set the table's selectable=false. After your input operation, use an onBlue event to set the table's selectable=true.

Pretty obnoxious behavior.

I had experienced this after wrapping two input boxes with a styled-component. The outer div completely broke the behavior. This is a terrible flaw in the design of this library.

So this is a pretty terrible workaround but the following works, mostly:

````
onSomeValueChanged = event => {
this.props.someValueChanged(event.target.value);

const { target } = event;
setTimeout(() => {
  target.focus();
}, 10);

};
````

Yeah, not pretty...

@scotmatson "Pretty obnoxious behavior...terrible flaw in the design of this library". This is free software, made by volunteers...good catch on the relation to styled-components though.

@scotmatson I can confirm that, at least in my case, the issue is resolved if I turn the Container = styled.div that I have as a wrapper into a normal, non-styled-component div.

Edit:
And I also share your frustrations. It would be really nice if these two libraries worked together flawlessly. This issue and the specificity issue are two things that I hope get ironed out soon but I am grateful for the people volunteering their time.

Edit2:
I Just discovered what was causing the problem in my case: declaring styled-component components in the render method! This was causing the components to be recreated on every re-render and React couldn't track which element had focus across the re-render boundary. This was also leading to a few other re-render issues for me.

Moving all my styled-component declarations to outside of my component resolved the losing focus issues for me. Probably a noob mistake but no one has mentioned that here so I thought I would come back and report.

The problem I seem to be having is that the focus is lot whenever a TextFields (or Input) is being rendered inside a object.map(). The TextFields outside the maps are working perfectly. Am I missing any props or anything else? I am totally lost

I tried almost every solution suggested in this thread without any luck.

The rough structure is this

Main File

...
    function Steps(props) {
      const { steps } = props;
      if(steps) {
        return steps.map((step, index) => (
          <div key={(step.order === '' ? index : step.order)}>
            <NewUxCard step={step} index={index} onChange={props.onChange} 
              onChangeCard={props.onChangeCard} onParamChange={props.onParamChange}/>
          </div>
        ));
      }
    }  

<div>
  <TextField/> //working
  <Steps steps={this.props.ux.steps} onChange={this.onChange} 
              onChangeCard={this.handleChangeCard} onParamChange={this.handleParamChange} /> 
</div>

NewUxCard

...
<div>
  <TextField type="text" fullWidth id={"qq"+this.props.index} label="Name" 
                    value={this.props.step.name} onChange={this.props.onChangeCard('name', this.props.index)} margin="normal" /> //not working - loses focus on each key stroke
</div>

Worth noting that I am using Redux-React, so the state changes goes through input->action->reducer

Any ideas? Thanks

EDIT 1:
Further investigation of the problem, I have narrowed it down and solved by removing the react function Steps(props) that is used to create a Tag (in this case).

So before, the structure was -> function Steps(props) -> Mapping Class . So now, I removed the function (the middle man) and just Const Steps -> Mapping Class . I can only speculate as to why this is working (perhaps some props were being ignored) but it solved my case.

@piltsen I just encountered this problem on v1.5.0 while rendering TextFields in a .map() - I narrowed it down to my TextField key being the same value as the input, so when it changed it likely caused the TextField to mount again and lose focus. Not sure if that helps you - though I'm sure by now you have sorted it

This is also a symptom of using render props. Doing something like

<HigherOrderComponent
    render={hocProps => <TextField value={...} onChange={...} />
/>

can cause this behaviour. To fix, pass a fragment instead if permitted by design:

<HigherOrderComponentThatDoesntPassOwnState
    component={<TextField  {...this.props}/>}
/>

In my case seems not focus was lost but cursor disappear from TextField. I can check that document.activeElement is equal to my TextField, I can use keyboard command and mouse wheel to change value (type number), but every type reducer run render happen and cursor lost.

Then in top of render I search by id my TextField and make

MyTextField.blur();
MyTextField.focus();

, which is not helping. But when I make focus() with some timeout, it seems to be working.

Edit:
Well, inside HOC it still not working, so it seems need to push it up anyway.

@younes0 I can confirm that fixes it for me too. In my case, I had a helper stateless component that would return a TextField with a few common props set and then spread the rest of the props onto it, so I didn't have to specify things like className or variant multiple times on the entire form. My text fields were losing focus as soon as any change was made to the form, and removing the helper function and just making raw TextFields over and over again fixed it.

Can this be re-opened, or should a new issue be filed?

I'm still getting this issue, I'm using 3.2.0

@OmarDavilaP Can you create a new issue with a reproduction?

I solved it for my case, you need to provide an unique id to that text field.
...
The id should not change after re-render though. This way React can keep track of which element was focused or not.
P.S. I had textfields in an array which is rendered via myArray.map, there you have to provide same keys when rerender.

EDIT: I tested again, just having the "key" on myArray to be the same across rerenders solved the issue.
The textfield id changed to shortid.generate().

    params.map(function(p,i){
        return(
           <div key={i}>              <--- this will always be the same for this particular row.
               <div className='param-inner'>
                   <TextField id={shortid.generate()} value = {p.value} onChange={this.updateParam.bind(this,i)}/>               <-- id here can be random, but it is necessary to avoid browser warning "we don't have enough information..."
                   </div>
               <div className='param-inner'>{p.unit}</div>
           </div>
        )
    }.bind(this));

Yup, it solves the issue in my case, basically I was using:

<NewTextField key={user.id+Math.random()}>
I replaced it by:

<NewTextField key={user.id}>
So, yes it seems a bad practice generate random Keys each time it re-render the component.

For me the issue was, I created a wrapper function for TextField, however i declared that function within the my component function, once moved outside my function everything worked as expected!

I created a sendbox sample https://codesandbox.io/s/material-demo-msbxl?fontsize=14&hidenavigation=1&theme=dark

I am having this problem with conditional rendering. I have a a javascript object in the render that returns various components in the .js file. I'm not describing it correctly, sorry. When I fix it I'll edit this post, if I remember to.

edit: Yay, I fixed it! I believe the way I was conditionally rendering was rendering each component even when it wasn't displayed. I moved the logic outside of the render and it seems to work fine now. Here's the old code:

edit: i can't into styling on this thing so nevermind.

I encountered this issue today and banged my head on it for a good few hours. In my case, what I was doing wrong was wrapping the TextField in a stateless component to better organise my page (don't render some if data is loading, etc etc), but I left the useState callback that controlled the TextField in the parent component.

For posterity, here's a small codesandbox link to reproduce my stupidity: https://codesandbox.io/s/mui-textedit-losefocus-g6xxb

The question now is, how would I create my custom component wrapping TextField? Both HOC and createElement seems to incur in this bug:

  const MyField= ({ name, ...props }) => (
    <TextField
      fullWidth
      variant="outlined"
      size="small"
      name={name}
      {...props}
    />
  );
  const MyField= ({ name, ...rest }) => {
    return React.createElement(TextField, {
      fullWidth: true,
      variant: "outlined",
      size: "small",
      name: name,
      {...rest}
    });
  };

Nevermind, the above approach works fine - I realized I was defined the custom component inside the parent component, which caused the issue. When moved outside of the parent scope (like it should be) it works fine - I updated the previous sandbox to show the correct approach: https://codesandbox.io/s/mui-textedit-losefocus-g6xxb

For me the issue was, I created a wrapper function for TextField, however i declared that function within the my component function, once moved outside my function everything worked as expected!

I created a sendbox sample https://codesandbox.io/s/material-demo-msbxl?fontsize=14&hidenavigation=1&theme=dark

This was my problem. Thank you!

Nevermind, the above approach works fine - I realized I was defined the custom component inside the parent component, which caused the issue. When moved outside of the parent scope (like it should be) it works fine - I updated the previous sandbox to show the correct approach: https://codesandbox.io/s/mui-textedit-losefocus-g6xxb

Thank you. This worked!

For me the issue was, I created a wrapper function for TextField, however i declared that function within the my component function, once moved outside my function everything worked as expected!

I created a sendbox sample https://codesandbox.io/s/material-demo-msbxl?fontsize=14&hidenavigation=1&theme=dark

@fleischman718 thanks for figuring this one out! Removing the wrapping component function for a TextField inside of a parent component's definition fixed my issue!

It took me a sec to figure out what was going on in the sandbox so I put a summary below:

import { TextField } from '@material-ui/core';

const ParentComponent = () => {

  // DON'T DO THIS!!!
  // fix issue by moving this component definition outside of the ParentComponent
  // or assign the TextField to a variable instead
  const TextFieldWrapper = () => (
    <TextField />
  );

  return (
    <div>
      <TextFieldWrapper />
    </div>
  )
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

garygrubb picture garygrubb  ·  57Comments

cfilipov picture cfilipov  ·  55Comments

nathanmarks picture nathanmarks  ·  100Comments

celiao picture celiao  ·  54Comments

darkowic picture darkowic  ·  62Comments