Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infinite loop if machine is created anew each render #196

Open
dceddia opened this issue Aug 11, 2020 · 0 comments
Open

Infinite loop if machine is created anew each render #196

dceddia opened this issue Aug 11, 2020 · 0 comments

Comments

@dceddia
Copy link

dceddia commented Aug 11, 2020

Hey, thanks for Robot! It's great. We're using it on a project to manage some tricky online/offline sync workflows and it has simplified everything quite a bit.

I'm actually in the middle of writing a tutorial around it, and hit upon what I think is a bug, but I'll describe what's happening.

My machine implements a "confirm to delete" flow, and it has an invoke state to call the function. Initially I had this deleteFn declared inside the file, hardcoded. Worked fine, but I wanted to parameterize it, so I wrapped the machine in a function...

const confirmationFlow = (deleteFn) =>
  createMachine({
    initial: state(transition("begin", "prompting")),
    prompting: state(
      transition("confirm", "loading"),
      transition("cancel", "initial")
    ),
    loading: invoke(
      deleteFn,
      transition("done", "initial"),
      transition(
        "error",
        "prompting",
        reduce((context, event) => {
          return {
            ...context,
            error: event.error
          };
        })
      )
    )
  });

And then I passed the function to useMachine:

const deleteThings = async () => { /* ... */ }

function App() {
  const [current, send] = useMachine(confirmationFlow(deleteThings));
  // ...
}

This creates an infinite loop because the useEffect inside useMachine is re-rendering every time the argument changes, which happens on every render.

One workaround is to pull the definition out...

const mac = confirmationFlow(deleteThings)

function App() {
  const [current, send] = useMachine(mac);
  // ...
}

But I think then the machine could be shared between multiple component instances, and can't access anything in the component scope like callbacks.

I can also work around this by passing the deleteFn function along with an event - parameterizing at call time, instead of init time.

Mostly, though, I sorta expected that useMachine would work the same as useState, where it "latches" the first value it sees and ignores future changes. I also tried passing a function to resolve the value (like useMachine(() => confirmationFlow(deleteThings))) but that throws an error.

I think one fix would be to change the behavior so that useMachine doesn't re-render when the passed value changes, but if that's undesirable, maybe supporting the function initializer approach would be a good in-between?

@matthewp matthewp transferred this issue from matthewp/robot-hooks Feb 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant