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

Async actions appear to be out of order #367

Closed
yawaramin opened this issue Sep 15, 2017 · 6 comments
Closed

Async actions appear to be out of order #367

yawaramin opened this issue Sep 15, 2017 · 6 comments
Labels
invalid This doesn't seem right question How do I do that?

Comments

@yawaramin
Copy link

I'm trying to use async actions. For simplicity, I'm using only a single action, called update, and dispatching all app actions through that. I'm also using the load and update events to set a state value on app startup and print the value on every state change, respectively.

Here's the code:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Hyperapp Async Test</title>
  </head>

  <body id="root">
    <script src="https://unpkg.com/hyperapp"></script></script>

    <script>
var { app, h } = hyperapp;
var initModel = 0;

// Helper to create an action payload in the correct shape.
function payload(key, value) { return { key: key, value: value }; }

function view(state, { update }) {
  return h("div", {}, [
    h("p", {}, "Current value: " + state.model),

    h("button",
      { onclick: _ => update(payload("increment")) },
      "Increment") ]);
}

function update(state, _, payload) {
  return function(update2) {
    switch (payload.key) {
      case "edit":
        return Promise.resolve({ model: payload.value }).then(update2);

      case "increment":
        return Promise.resolve({ model: state.model + 1 }).then(update2);

      case "log":
        console.log(payload.value);
        return Promise.resolve(state).then(update2);
    }
  };
}

var events = {
  load: function(_, { update }) {
    // Set the value to 5 when the app loads.
    return update(payload("edit", 5));
  },

  update: function(_, { update }, nextState) {
    // Log the value whenever it changes.
    return update(payload("log", nextState));
  }
};

app({
  state: { model: initModel },
  view: view,
  actions: { update: update },
  events: events
});
    </script>
  </body>
</html>

I'm having two problems:

  1. State updates appear out of order. First state is { model: 5 }, then it's { model: 0 }, while my understanding is it should be the other way round.

  2. State seems to become { model: undefined } after the load event fires, so the view renders as Current value: undefined, and clicking the Increment button logs Object { model: NaN }.

Any suggestions on fixing these async updates? Thanks

@andyrj
Copy link
Contributor

andyrj commented Sep 15, 2017

load event is special, return from it is node, which is used for dom hydration. If you aren't hydrating from ssr/pre-render you shouldn't return from load.

@yawaramin
Copy link
Author

yawaramin commented Sep 15, 2017

Oops, missed that looking through the docs. OK, if I get rid of the return from the event handlers so that neither of them returns anything, the script seems to go into an infinite loop continuously changing the state between Object { model: 5 } and Object { model: 0 } and printing to the console. The view doesn't get a chance to render.

@rantecki
Copy link

Since you're using promises, you may need to add this boilerplate described on https://github.com/hyperapp/hyperapp/blob/master/docs/events.md, in order to allow actions to return a Promise.

app({
  events: {
    resolve(state, actions, result) {
      if (result && typeof result.then === "function") {
        return update => result.then(update) && result
      }
    }
  }
})

@yawaramin
Copy link
Author

Same result ... which makes sense, if I understand the documentation right the resolve event doesn't affect how other event handlers behave...

@yawaramin
Copy link
Author

OK, I figured it out. The key is something similar to what @andyrj said--instead of returning a call to update with the existing state from the "log" case, I changed it to return undefined. It seems that the update function in async updates (which I called update2 above to avoid ambiguity) will force a state update even if the state value hasn't changed. In turn, this will trigger my update event handler, which will call the update action, which will force a state update again, and so on, ad infinitum. Neat little infinite loop.

TL;DR: avoid potential infinite loop by not calling the async update function if the state hasn't actually changed.

@jorgebucaran jorgebucaran added invalid This doesn't seem right question How do I do that? labels Sep 16, 2017
@jorgebucaran
Copy link
Owner

@yawaramin will force a state update even if the state value hasn't changed

How could we determine if the state changed or not? We could check if appState holds the same reference or if it's a new reference, and if it's the same skip the update, already talked about here and the consensus was to let you have it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
invalid This doesn't seem right question How do I do that?
Projects
None yet
Development

No branches or pull requests

4 participants