Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React-redux: should the render always happen in the same tick as dispatching an action?

In my react-redux application, I have a controlled text input. Every time the component changes value, it dispatches an action and in the end, the value comes back through the redux loop and is rendered.

In the example below this works well, but in practice, I've run into an issue where the render happens asynchronously from the action dispatch and the input loses cursor position. To demonstrate the issue, I've added another input with a delay explicitly put in. Adding a space in the middle of a word causes the cursor to skip in the async input.

I have two theories about this and would like to know which one is true:

  • This should work, but I have a bug somewhere in my production application that causes the delay
  • The fact that it works in the simple example is just luck and react-redux doesn't guarantee that render would happen synchronously

Which one is right?

Working example:

http://jsbin.com/doponibisi/edit?html,js,output

const INITIAL_STATE = {
  value: ""
};

const reducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case 'SETVALUE':
      return Object.assign({}, state, { value: action.payload.value });
    default:
      return state;
  }
};

const View = ({
  value,
  onValueChange
}) => (
  <div>
    Sync: <input value={value} onChange={(e) => onValueChange(e.target.value)} /><br/>
    Async: <input value={value} onChange={(e) => { const v = e.target.value; setTimeout(() => onValueChange(v), 0)}} />
  </div>
);

const mapStateToProps = (state) => {
  return {
    value: state.value
  };
}

const mapDispatchToProps = (dispatch) => {
  return {
    onValueChange: (value) => {
      dispatch({
        type: 'SETVALUE',
        payload: {
          value
        } 
      })            
    }
  };
};


const { connect } = ReactRedux;
const Component = connect(
  mapStateToProps,
  mapDispatchToProps
)(View);

const { createStore } = Redux;
const store = createStore(reducer);

ReactDOM.render(
  <Component store={store} />,
  document.getElementById('root')
);

EDIT: Clarifying question

Marco and Nathan have both correctly pointed out that this is a known issue in React that won't be fixed. If there is a setTimeout or other delay between onChange and setting the value, the cursor position will be lost.

However, the fact that setState just schedules an update is not enough to cause this bug to happen. In the Github issue that Marco linked, there is a comment:

Loosely speaking, setState is not deferring rendering, it's batching updates and executing them immediately when the current React job has finished, there will be no rendering frame in-between. So in a sense, the operation is synchronous with respect to the current rendering frame. setTimeout schedules it for another rendering frame.

This can be seen in JsBin example: the "sync" version also uses setState, but everything is working.

The open question still is: is there something inside of Redux that creates a delay that lets a rendering frame in-between, or could Redux be used in a way that avoids those delays?

Workarounds for the issue at hand are not needed, I found one that works in my case but I'm interested in finding out the answer to the more general question.

EDIT: issue solved

I was happy with Clarks answer and even awarded the bounty, but it turns out it was wrong when I really tested it by removing all middlewares. I also found the github issue that is related to this.

https://github.com/reactjs/react-redux/issues/525

The answer is:

  • this is an issue in react-redux that will be fixed with react-redux 5.1 and react v16
like image 636
OlliM Avatar asked Feb 24 '17 10:02

OlliM


People also ask

What happens when we dispatch an action in Redux?

The app code dispatches an action to the Redux store, like dispatch({type: 'counter/incremented'}) The store runs the reducer function again with the previous state and the current action , and saves the return value as the new state.

Does Dispatch cause re render?

When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, the component will not re-render.

What happens when you try to dispatch an action within a reducer?

Dispatching an action within a reducer is an anti-pattern. Your reducer should be without side effects, simply digesting the action payload and returning a new state object. Adding listeners and dispatching actions within the reducer can lead to chained actions and other side effects.

What happens after dispatch React?

It's called a middleware. A middleware can intercept any action triggered by a dispatch, also it has access to the store. At the end of your middleware your just use a callback to tell the flow to continue. You need to pass your action to it so your reducers can finally be called and receive the action.


1 Answers

What middleware are you using in your Redux application? Perhaps one of them is wrapping a promise around your action dispatches. Using Redux without middleware does not exhibit this behaviour, so I think it's probably something specific to your setup.

like image 69
Clark Pan Avatar answered Jan 24 '23 10:01

Clark Pan