Xstate: Reactjs doing unnecessary renders

Created on 29 Sep 2020  路  7Comments  路  Source: davidkpiano/xstate

Description

As discussed on spectrum chat.
When sending events to the machine, even when those events don't resolve to a new state transition or context change, reactjs still re-renders components.

Expected Result

There should be no re-rendering of the components that use the machine in question.

Actual Result

Components are being re-rendered.

Reproduction
https://codesandbox.io/s/xstate-react-render-ssnc1

enhancement 鈿涳笌 @xstatreact

Most helpful comment

Okay, so should we go ahead with preventing rerenders if only actions changed?

I believe yes - this would be best as their change shouldn't affect the render output in any meaningful way.

All 7 comments

Thinking through this more, this might be part of a bigger story - it's a valid use-case for the function component to be called again (rerender) if actions changed, if something should be done with those actions:

const [state, send] = useMachine(...);

useEffect(() => {
  state.actions.forEach(action => {
    // do something with each action
  });
}, [state.actions]); // should execute effect when only actions change

// ...

But this opens the conversation for supporting some sort of "selector" that will only select and map over each state; something like:

// 鈿狅笍 TENTATIVE API

const [name, send] = useMachine(someMachine, {
  // only care about specific parts of the state
  select: state => state.context.firstName = state.context.lastName
});

// component will only rerender when `firstName` or `lastName` changes

// ...

it's a valid use-case for the function component to be called again (rerender) if actions changed, if something should be done with those actions:

This is a totally invalid use case in React's world. If one specifies actions to be executed during some transitions we should strive for a guarantee that they will always be executed, which is not the case with the given code. React has a batching mechanism and it's there from the beginning (or close to that 馃槄 ) so even before hooks this would yield the expected results (if you would just implement handling actions in componentDidMount/componentDidUpdate)

it's a valid use-case for the function component to be called again (rerender) if actions changed, if something should be done with those actions:

This is a totally invalid use case in React's world. If one specifies actions to be executed during some transitions we should strive for a guarantee that they will always be executed, which is not the case with the given code. React has a batching mechanism and it's there from the beginning (or close to that 馃槄 ) so even before hooks this would yield the expected results (if you would just implement handling actions in componentDidMount/componentDidUpdate)

I'm confused; can you show an example where this guarantee is not being held? Or are you saying that we _should_ deprecate the idea that actions can be handled like my example above?

I'm confused; can you show an example where this guarantee is not being held?

Take a look: https://codesandbox.io/s/blue-shadow-cnjz2?file=/src/App.js . Notice how barAction is missed.

Ah you're right; so it's an invalid use-case anyway (unfortunate how batching works like that).

Okay, so should we go ahead with preventing rerenders if only actions changed?

Okay, so should we go ahead with preventing rerenders if only actions changed?

I believe yes - this would be best as their change shouldn't affect the render output in any meaningful way.

Thinking through this more, this might be part of a bigger story - it's a valid use-case for the function component to be called again (rerender) if actions changed, if something should be done with those actions:

const [state, send] = useMachine(...);

useEffect(() => {
  state.actions.forEach(action => {
    // do something with each action
  });
}, [state.actions]); // should execute effect when only actions change

// ...

I believe this can already be done with asEffect

const [current, send]= useMachine(InputMachine, {
  actions: {
     UI_FOCUS : asEffect((context, event)=>setFocus(context.isFocused))
  }
})
Was this page helpful?
0 / 5 - 0 ratings