56

I got my input who is filled by a value from my state.

<input id="flashVars" name="flashVars" type="text" value={settings.flashVarsValue} disabled={isDisabled} onChange={handleChange} />

Settingsis my state with Redux. When i put a value into my input, i must specify a onChange function. This is my onChange function:

handleFlashVarsChange(e) {
  let { dispatch } = this.props;

  dispatch( changeFlashVarsValue(e.target.value) );
}

It change the state value flashVarsValue for the value of the input. But when i type in my input, it lags. I don't understand why i should call the dispatch each time i change the input value.

Is there any way who can give less lags?

My reducer:

import { ACTIONS } from '../utils/consts';

const initialState = {
  ...
  flashVarsValue: '',
  ...
};

export function formSettings(state = initialState, action = '') {
  switch (action.type) {

    ...

    case ACTIONS.CHANGE_FLASHVARS_VALUE:
      return Object.assign({}, state, {
        flashVarsValue: action.data
      });

    default:
      return state;
  }
}

My action:

export function changeFlashVarsValue(data) {
  return {
    type: ACTIONS.CHANGE_FLASHVARS_VALUE,
    data: data
  }
}

Thank you

Mike Boutin
  • 5,297
  • 12
  • 38
  • 65

7 Answers7

28

I had a similar problem when I was editing a grid with a million rows, so what I did was to change the update logic, in your case handleChange to be called only on the event onBlur instead of onChange. This will only trigger the update when you lose focus. But don't know if this would be a satisfactory solution for you.

phoenix
  • 7,988
  • 6
  • 39
  • 45
luanped
  • 3,178
  • 2
  • 26
  • 40
  • Thank you ill try it monday ;) – Mike Boutin Nov 08 '15 at 02:35
  • @MikeBoutin did it work for you? i have the same issue – Antoine Feb 05 '16 at 01:31
  • Try to manage the update with shouldComponentUpdate, it worked for me – Mike Boutin Feb 05 '16 at 01:42
  • What do you mean "manage the update with shouldComponentUpdate"? – lux Mar 01 '16 at 16:37
  • 4
    Not sure if I'm the only person with this issue, but if I use `onBlur` instead of `onChange`, the field does not update as I type. – besseddrest Dec 07 '17 at 20:23
  • 6
    If you use `onBlur` instead of `onChange` in any input and set the property `value` you have to use `defaultValue` instead. This way it will work for controlled components. Also remember that every time the input is blurred, the function will be called. [Working example](https://codesandbox.io/s/q380ywxo26) – c-chavez Sep 02 '18 at 14:17
  • The `onBlur` thingy works well, but `defaultValue` behaves weirdly. I use it inside a controlled component and even if the prop value is certainly null, the amount shown on the screen remains somehow cached to the previous value. – Paul Razvan Berg Dec 02 '19 at 02:24
  • Most underrated answer on SO. You're the best @luanped. – tylerwillis May 28 '20 at 15:50
  • 1
    this is working, but the question IS THIS THE BEST SOLUTION yet? – Aljohn Yamaro Jun 07 '20 at 02:04
  • @besseddrest same here. since im downloading data, the defaultValue starts as null, but im not sure why it does not update once the value is populated. – chitgoks Jun 07 '20 at 02:14
4

The issue here is possibly re-renders. You're passing in "settings" (your entire state) to your component containing the "input", and we don't know how the rest of your connected components are coupled to state. Check to see if as a result of the state object mutating, you're rerendering much more than just the input on every keystroke. The solution to this is to more directly pass in the specific parts of state you need from mapStateToProps (in this case, maybe only pass in "flashVarsValue" if that's all this component needs, and make sure other components aren't also passed the whole state) and use PureRenderMixin or Dan Abramov's https://github.com/gaearon/react-pure-render if you're using ES6 components to not re-render if your props haven't changed

Royi Hagigi
  • 240
  • 1
  • 10
4

The answer for me was to use the shouldComponentUpdate lifecycle hook. This has already been given as an answer in a comment by Mike Boutin (about a year ago :) ), but an example might help the next visitor here.

I had a similar problem, with the text input being lost, and slow and jumpy. I was using setState to update the formData in my onChange event.

I found that the form was doing a complete re-render with every keypress, as the state had changed. So to stop this, I overrode the function:

shouldComponentUpdate(nextProps, nextState) {
   return this.state.formErrors !== nextState.formErrors);
}

I show an error notification panel on form submission with any new or changed validation errors, and that's the only time I need to re-render.

If you have no child components, you could probably just set the form component's shouldComponentUpdate to always return false.

ben.tiberius.avery
  • 2,194
  • 1
  • 13
  • 8
  • From react documentation "This method only exists as a performance optimization. Do not rely on it to “prevent” a rendering, as this can lead to bugs." (https://reactjs.org/docs/react-component.html) – daliusd Dec 18 '18 at 08:01
  • I believe event handler execution will be first in order before shouldCompomentUpdate, so this will not work in all cases. – c g Jan 10 '19 at 18:04
4

I know this is an old question, but if you want to fire onChange on a text input, you probably want to debounce your event. This thread does a good job breaking it down, but I think this would work for the op's example:

import debounce from 'debounce'                                      

function debounceEventHandler(...args) {
  const debounced = debounce(...args)
  return function (e) {
    e.persist();
    return debounced(e);
  }
}                                                                      
const Container = React.createClass({
  handleFlashVarsChange(e) {
    let { dispatch } = this.props;
    //basic redux stuff
    this.props.changeFlashVarsValue(e.target.value));
  },
  render() {
    const handleChange = debounceEventHandler(this.handleFlashVarsChange, 15);
    return (
      <input id="flashVars" onChange={handleChange} />
    )
  }                                                                         
}
//...prep and return your redux container
Joe
  • 185
  • 2
  • 4
  • 2
    I was thinking about this - BUT - if your debounce sets state after 1 second, and your input relies on a change in state to update (re-render), wouldn't you have to wait 1 second to see 'foo' change to 'bar', for example? You wouldn't actually see the individual 'b', 'ba', 'bar' changes, would you? – besseddrest Dec 07 '17 at 20:36
  • 5
    this only works with uncontrolled components, if you use a controlled component and the value is debounced, the input won't update. – pgarciacamou Dec 27 '17 at 19:16
3

The answer is not to re-render your component on every key stroke, only if user stops typing in. Something like this:

shouldComponentUpdate(nextProps, nextState) {
    if (!textInputReRender)
        return false;
    else
        return true;
}

onTextInputChange = (propName, propValue) => {
    if (inputChangeTimerId)
        clearTimeout(inputChangeTimerId);

    inputChangeTimerId = setTimeout(() => {
        inputChangeTimerId = null;
        const newState = {};

        textInputReRender = true;

        newState[propName] = propValue;
        this.setState(newState);
    }, 500);

    textInputReRender = false;
}
Agent Coop
  • 392
  • 4
  • 12
0

Use onChangeText instead

import { TextInput, View} from "react-native";  
import { connect } from "react-redux"; 
import React, { Component, useState } from "react";

function SearchTextInput(props) {
  const { keyword = "", setKeyword } = props; 
  return (
     <TextInput
       style={styles.searchInputText}
       placeholder={"placeholder here"}
       value={keyword}
       onChangeText={setKeyword(t)}
     />
  );
}

const mapStateToProps = state => {
  return {
    keyword: state.search.keyword,
    search: state.search
  };
};
const mapDispatchToProps = dispatch => ({
  setKeyword: payload => dispatch(({type:'updateSearchText', keyword: payload }))
});
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(SearchTextInput);
Cary Law
  • 27
  • 2
0

The problem is common happen when you have a complex page, that need re-render always that you update the state. So you can percept that is very slow when typing.

I found a solution: The cycle of life of react run by component. So you can create other component and manage yours events, like onChange, after you can call onBlur, that is passed by props for him. It worked for me:

import React, {Fragment, useState, useEffect} from 'react';
import TextField from '@material-ui/core/TextField';


export function InputText(props) {
  const [value, setValue] = useState("");
  const {onBlur = (e) => {console.log(e)}, name='name', defaultValue= 'Default', type='text', label='Label'} = props

  useEffect(() => {

    // console.log(value);
  })

  return (
      <label>
        <TextField 
          name={name} 
          label={label}  
          onBlur={e => onBlur(e)}
          type={type}
          value={value}
          onChange={e => setValue(e.target.value)}
        />
      </label>
  );
}


class Sample extends React.Component {
    handleBlurInput = e => {        
         this.setState({ [e.target.name]: e.target.value });
    };
  render() {
    return (
    <InputText name="nome" label="Label Sample" defaultValue={this.state.nome} onBlur={this.handleBlurInput.bind(this)} />

    // Complex app ....
    );
  }
}
McQuade
  • 131
  • 1
  • 4