admin管理员组

文章数量:1026989

So I am using a hash to store the values of dynamically created rows of input values and I lose focus on the input I am modifying after entering only one character. I think the solution to this may be to use refs to refocus on only the last input changed, but I couldn't get it to work, as I wasn't able to figure out how to specify which element was last changed. Advice on how to solve this is appreciated.

The code below dynamically creates input boxes, and looks up their values based on the unitPriceValueHash. Each variant has an id, and id is used as the key to the hash.

I created a codepen to try and recreate the problem, but the issue im facing doesn't show up in code pen. In my actual app I press 1 for example in the input box, then the cursor is not on the input box anymore.

The only difference between the codepen and my code appears to be the fact the the inputs are nested inside a table.

  CreateItem(variant) {
    const unitPriceValueHash = this.props.unitPriceValueHash
    return { 
      variant_title: variant.variant_title,
      variant_price: variant.variant_price,
      unit_cost: <TextField 
                  type="number"
                  onChange={(event) => this.handleUnitPriceChange(variant.id, event)}
                  key={variant.id}
                  value={unitPriceValueHash[variant.id] || ''}
                 />
    };
  }

Below is the change of state that modifies the hash

  handleUnitPriceChange (id, event) {
    const unitPriceValueHash = this.state.unitPriceValueHash
    unitPriceValueHash[id] = event
    console.log(unitPriceValueHash)
    this.setState({unitPriceValueHash: unitPriceValueHash});
    //this.updateVariantUnitCost(id, event);
  }

So I am using a hash to store the values of dynamically created rows of input values and I lose focus on the input I am modifying after entering only one character. I think the solution to this may be to use refs to refocus on only the last input changed, but I couldn't get it to work, as I wasn't able to figure out how to specify which element was last changed. Advice on how to solve this is appreciated.

The code below dynamically creates input boxes, and looks up their values based on the unitPriceValueHash. Each variant has an id, and id is used as the key to the hash.

I created a codepen to try and recreate the problem, but the issue im facing doesn't show up in code pen. In my actual app I press 1 for example in the input box, then the cursor is not on the input box anymore.

https://codepen.io/ByteSize/pen/oogLpE?editors=1011

The only difference between the codepen and my code appears to be the fact the the inputs are nested inside a table.

  CreateItem(variant) {
    const unitPriceValueHash = this.props.unitPriceValueHash
    return { 
      variant_title: variant.variant_title,
      variant_price: variant.variant_price,
      unit_cost: <TextField 
                  type="number"
                  onChange={(event) => this.handleUnitPriceChange(variant.id, event)}
                  key={variant.id}
                  value={unitPriceValueHash[variant.id] || ''}
                 />
    };
  }

Below is the change of state that modifies the hash

  handleUnitPriceChange (id, event) {
    const unitPriceValueHash = this.state.unitPriceValueHash
    unitPriceValueHash[id] = event
    console.log(unitPriceValueHash)
    this.setState({unitPriceValueHash: unitPriceValueHash});
    //this.updateVariantUnitCost(id, event);
  }
Share Improve this question edited Nov 6, 2017 at 23:28 ByteMe asked Oct 30, 2017 at 2:00 ByteMeByteMe 1,4022 gold badges18 silver badges31 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 1

There's a couple problems with the code you've shared.

  • Don't use inline functions. Each render, the function is created again which means that when react pares the props, it looks like the function is different (it is a new/different function each time!) and react will re-render.
  • Don't modify any objects which exist in the state, instead create a new object. If you modify an object that exists in the state, you're essentially saying you don't want renders to be consistent and reproducible.

I've re-posted your original code with the issues highlighted

CreateItem(variant) {
  const unitPriceValueHash = this.props.unitPriceValueHash
  return {
    variant_title: variant.variant_title,
    variant_price: variant.variant_price,
    unit_cost: <TextField
      type="number"
      onChange={(event) => this.handleUnitPriceChange(variant.id, event)}
      // ^^^^ - inline functions cause react to re-render every time, instead - create a ponent
      key={variant.id}
      value={unitPriceValueHash[variant.id] || ''}
    />
  };
}

handleUnitPriceChange(id, event) {
  const unitPriceValueHash = this.state.unitPriceValueHash
  unitPriceValueHash[id] = event
  // ^^^^ - please, please - don't do this. You can't mutate the state like this.

  // instead, do the following to create a new modified object without modifying the object in the state
  const unitPriceValueHash = Object.assign({}, this.state.unitPriceValueHash, { id: event });
  this.setState({ unitPriceValueHash: unitPriceValueHash });
}

In regards to the inline-function, generally the remendation is to create a new ponent for this which takes the value as a prop. That might look like this:

class UnitCost extends PureComponent {
  static propTypes = {
    variantId: PropTypes.number,
    variantValue: PropTypes.object,
    onUnitPriceChange: PropTypes.func,
  }
  handleUnitPriceChange(e) {
    this.props.onUnitPriceChange(this.props.variantId, e)
  }
  render() {
    return (
      <TextField
        type="number"
        onChange={this.handleUnitPriceChange}
        value={this.props.variantValue || ''}
      />
    );
  }
}

CreateItem(variant) {
  const unitPriceValueHash = this.props.unitPriceValueHash
  return {
    variant_title: variant.variant_title,
    variant_price: variant.variant_price,
    unit_cost: (
      <UnitCost
        key={variant.id}
        variantId={variant.id}
        variantValue={unitPriceValueHash[variant.id]}
        onUnitPriceChange={this.handleUnitPriceChange}
      />
    ),
  };
}

Regarding your concerns about focus, react generally won't lose your object focus when re-rendering, so don't ever, ever re-focus an object after an update for this reason.

The only time react will lose focus, is if it pletely discards the current DOM tree and starts over from scratch. It will do this if it thinks a parent object has been replaced instead of modified. This can happen because of a missing key prop, or a key prop that has changed.

You have not posted enough code for us to investigate this further. If you want more help you should build a minimum reproducible example that we can run and test.

The solution to this problem had me use an intermediate state to store the value of the input field on change, and a submit AJAX request on an onBlur

class TextFieldWrapper extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: this.props.variantValue[this.props.variantId] || '',
    }
    this.handleUnitPriceChange = this.handleUnitPriceChange.bind(this)
    this.updateValue = this.updateValue.bind(this)
  }
  updateValue(value){
    this.setState({
      value: value,
    });
  }
  handleUnitPriceChange() {
    this.props.onUnitPriceChange(this.props.variantId, this.state.value);
  }
  render(){
    return (
      <TextField
        type="number"
        id={this.props.variantId}
        key={this.props.variantId}
        onChange={this.updateValue}
        onBlur={this.handleUnitPriceChange}
        value={this.state.value}
      />
    );
  }
}

So I am using a hash to store the values of dynamically created rows of input values and I lose focus on the input I am modifying after entering only one character. I think the solution to this may be to use refs to refocus on only the last input changed, but I couldn't get it to work, as I wasn't able to figure out how to specify which element was last changed. Advice on how to solve this is appreciated.

The code below dynamically creates input boxes, and looks up their values based on the unitPriceValueHash. Each variant has an id, and id is used as the key to the hash.

I created a codepen to try and recreate the problem, but the issue im facing doesn't show up in code pen. In my actual app I press 1 for example in the input box, then the cursor is not on the input box anymore.

The only difference between the codepen and my code appears to be the fact the the inputs are nested inside a table.

  CreateItem(variant) {
    const unitPriceValueHash = this.props.unitPriceValueHash
    return { 
      variant_title: variant.variant_title,
      variant_price: variant.variant_price,
      unit_cost: <TextField 
                  type="number"
                  onChange={(event) => this.handleUnitPriceChange(variant.id, event)}
                  key={variant.id}
                  value={unitPriceValueHash[variant.id] || ''}
                 />
    };
  }

Below is the change of state that modifies the hash

  handleUnitPriceChange (id, event) {
    const unitPriceValueHash = this.state.unitPriceValueHash
    unitPriceValueHash[id] = event
    console.log(unitPriceValueHash)
    this.setState({unitPriceValueHash: unitPriceValueHash});
    //this.updateVariantUnitCost(id, event);
  }

So I am using a hash to store the values of dynamically created rows of input values and I lose focus on the input I am modifying after entering only one character. I think the solution to this may be to use refs to refocus on only the last input changed, but I couldn't get it to work, as I wasn't able to figure out how to specify which element was last changed. Advice on how to solve this is appreciated.

The code below dynamically creates input boxes, and looks up their values based on the unitPriceValueHash. Each variant has an id, and id is used as the key to the hash.

I created a codepen to try and recreate the problem, but the issue im facing doesn't show up in code pen. In my actual app I press 1 for example in the input box, then the cursor is not on the input box anymore.

https://codepen.io/ByteSize/pen/oogLpE?editors=1011

The only difference between the codepen and my code appears to be the fact the the inputs are nested inside a table.

  CreateItem(variant) {
    const unitPriceValueHash = this.props.unitPriceValueHash
    return { 
      variant_title: variant.variant_title,
      variant_price: variant.variant_price,
      unit_cost: <TextField 
                  type="number"
                  onChange={(event) => this.handleUnitPriceChange(variant.id, event)}
                  key={variant.id}
                  value={unitPriceValueHash[variant.id] || ''}
                 />
    };
  }

Below is the change of state that modifies the hash

  handleUnitPriceChange (id, event) {
    const unitPriceValueHash = this.state.unitPriceValueHash
    unitPriceValueHash[id] = event
    console.log(unitPriceValueHash)
    this.setState({unitPriceValueHash: unitPriceValueHash});
    //this.updateVariantUnitCost(id, event);
  }
Share Improve this question edited Nov 6, 2017 at 23:28 ByteMe asked Oct 30, 2017 at 2:00 ByteMeByteMe 1,4022 gold badges18 silver badges31 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 1

There's a couple problems with the code you've shared.

  • Don't use inline functions. Each render, the function is created again which means that when react pares the props, it looks like the function is different (it is a new/different function each time!) and react will re-render.
  • Don't modify any objects which exist in the state, instead create a new object. If you modify an object that exists in the state, you're essentially saying you don't want renders to be consistent and reproducible.

I've re-posted your original code with the issues highlighted

CreateItem(variant) {
  const unitPriceValueHash = this.props.unitPriceValueHash
  return {
    variant_title: variant.variant_title,
    variant_price: variant.variant_price,
    unit_cost: <TextField
      type="number"
      onChange={(event) => this.handleUnitPriceChange(variant.id, event)}
      // ^^^^ - inline functions cause react to re-render every time, instead - create a ponent
      key={variant.id}
      value={unitPriceValueHash[variant.id] || ''}
    />
  };
}

handleUnitPriceChange(id, event) {
  const unitPriceValueHash = this.state.unitPriceValueHash
  unitPriceValueHash[id] = event
  // ^^^^ - please, please - don't do this. You can't mutate the state like this.

  // instead, do the following to create a new modified object without modifying the object in the state
  const unitPriceValueHash = Object.assign({}, this.state.unitPriceValueHash, { id: event });
  this.setState({ unitPriceValueHash: unitPriceValueHash });
}

In regards to the inline-function, generally the remendation is to create a new ponent for this which takes the value as a prop. That might look like this:

class UnitCost extends PureComponent {
  static propTypes = {
    variantId: PropTypes.number,
    variantValue: PropTypes.object,
    onUnitPriceChange: PropTypes.func,
  }
  handleUnitPriceChange(e) {
    this.props.onUnitPriceChange(this.props.variantId, e)
  }
  render() {
    return (
      <TextField
        type="number"
        onChange={this.handleUnitPriceChange}
        value={this.props.variantValue || ''}
      />
    );
  }
}

CreateItem(variant) {
  const unitPriceValueHash = this.props.unitPriceValueHash
  return {
    variant_title: variant.variant_title,
    variant_price: variant.variant_price,
    unit_cost: (
      <UnitCost
        key={variant.id}
        variantId={variant.id}
        variantValue={unitPriceValueHash[variant.id]}
        onUnitPriceChange={this.handleUnitPriceChange}
      />
    ),
  };
}

Regarding your concerns about focus, react generally won't lose your object focus when re-rendering, so don't ever, ever re-focus an object after an update for this reason.

The only time react will lose focus, is if it pletely discards the current DOM tree and starts over from scratch. It will do this if it thinks a parent object has been replaced instead of modified. This can happen because of a missing key prop, or a key prop that has changed.

You have not posted enough code for us to investigate this further. If you want more help you should build a minimum reproducible example that we can run and test.

The solution to this problem had me use an intermediate state to store the value of the input field on change, and a submit AJAX request on an onBlur

class TextFieldWrapper extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: this.props.variantValue[this.props.variantId] || '',
    }
    this.handleUnitPriceChange = this.handleUnitPriceChange.bind(this)
    this.updateValue = this.updateValue.bind(this)
  }
  updateValue(value){
    this.setState({
      value: value,
    });
  }
  handleUnitPriceChange() {
    this.props.onUnitPriceChange(this.props.variantId, this.state.value);
  }
  render(){
    return (
      <TextField
        type="number"
        id={this.props.variantId}
        key={this.props.variantId}
        onChange={this.updateValue}
        onBlur={this.handleUnitPriceChange}
        value={this.state.value}
      />
    );
  }
}

本文标签: javascriptreactjs input element loses focus after keystrokeStack Overflow