Hacker News new | past | comments | ask | show | jobs | submit login

It's my fault for not fully understanding the functional universe I was getting into when I first started working on Hyperapp. The latest Hyperapp is more strict, but it's all in good measure. Lifecycle events are impure, that's why they're no-good.

I can tell you that Hyperapp is not for everyone. If you want to write pure, immutable, functional JavaScript and think hard about client side app architecture (unidirectional state management, controlled side effects, toggleable subscriptions), then you'll love it. I also suggest looking at Elm while you're at it.

If you are looking for a more accommodating, meet-in-the-middle kind of approach where you can mix programming styles, you might be better served by, say, P/React.




If we talk about functional API, did you consider providing a push method, like with a Promise, to express the updating of an effect or a subscription ?

(the function 'update' in the code below)

  app({
    init: 0,
    view: state =>
      h("div", {}, [
        h("h1", {}, state),
        h("button", { onclick: (state, event, update) => {
          window.setTimeout(() => update(state => state - 1), 1000);
        } }, "subtract"),
        h("button", { onclick: state => state + 1 }, "add")
      ]),
    node: document.getElementById("app")
  })


We built it into the framework, if you happen to be familiar with Elm, this should make sense right away, but here's how you'd write something like that using Hyperapp:

    import { h, app } from "hyperapp"
    import { delay } from "@hyperapp/time"

    const Decrement = (state) => state - 1

    app({
      init: 0,
      view: (state) =>
        h("div", {}, [
          h("h1", {}, state),
          h("button", {
              onclick: (state/*, event*/) => [state, delay(100, Decrement)],
            }, "subtract"
          ),
          h("button", { onclick: (state) => state + 1 }, "add"),
        ]),
      node: document.getElementById("app"),
    })

See how you never actually called setTimeout as that would be a side effect.

Instead, we have "controlled effects" in Hyperapp. This `delay` function doesn't even call setTimeout itself, but return an object that tells Hyperapp how. Just like how `h("button")` or `<button>` with JSX doesn't actually create a button, but an object representation of it.

Finally this part: [state, delay(100, Decrement)], only placed here for convenience (as you'll usually want that in its own action) is how you tell Hyperapp to do the effect when the button is clicked. This is the same (model Cmd) continuation pattern used in Elm.


I'm familiar with Elm but i think the model of Hyperapp is better than the [state command] pattern. This is how i see Hyperapp, the event part of Hyperapp is not about commands, it's about transition functions, functions that takes a before state and returns an after state. Hyperapp doesn't let the user to control the state by itself.

But there is worst, the problem with the pattern [state command] is that it doesn't work well with async method, because at the time the command is called, the state which is passed alongside the command and the state maintained by Hyperapp may be different.

With your example, the issue is that Decrement may be called on a previous state and not on the actual state.

The idea of the updater is to provide the function that takes the transition function as parameter, so delay will be written that way

  function delay(updater, timeout, fun) {
    window.setTimeout(() => updater(fun), timeout);
  }
If you want to decrement the state after a delay using the state at that time

  h("button", {
    onclick: (state, _, updater) => delay(updater, 100, Decrement),
    }, "subtract")
(from the Hyperapp perspective, the event listener return undefined so you don't have to update the state at the time the event listener is called)

and if you want to decrement the state using the state at the time the user click

  h("button", {
    onclick: (state, _, updater) => delay(updater, 100, _ => Decrement(state)),
    }, "subtract")
The other benefit is that because delay() takes an updater as first argument, it's clear that the function delay does a side effect. Conceptually, the idea is that instead of trying to hide the side effect, you make it clear to the developer (more like Haskell does).


My example actually has no issues, but [here is an example][1] so you can confirm that. I included the implementation of `delay` there as well so you can also see what happens behind the scenes.

---

So, in Hyperapp there are no async/await functions. Or Promises. Can't use functions that create side effects. Can't use setTimeout, setInterval, new Promise, fetch, add/removeEventListener, etc.

delay(100, Action) looks familiar to setTimeout, but it's just a function that returns an object that tells Hyperapp what to do. It doesn't do anything by itself. You can write your own delay function too if you want as long as you give Hyperapp what it wants.

The state becoming stale is never an issue in Hyperapp, because you get a fresh copy of it inside actions, which is the only place where you can "update" the state.

[1]: https://codesandbox.io/s/hyperapp-minimal-counter-with-effec...


Thanks for taking the time to explain me how it works. So you're right that you can not use a previous state.

And if i want to get the dispatcher directly, i can write a helper function

  function effect(fun) {
    return state => [state, [dispatcher => fun(dispatcher)]]
  }
and use it to access to the dispatcher/updater

  h(
    "button",
    { onclick: effect(updater => setTimeout(() => updater(Decrement), 1000)) },
    "subtract"
  )
Thanks a lot !


Basically, yes!

In practice, we don't advocate users to reach out for dispatch, as Hyperapp intends to provide all the fx/subscriptions building blocks you need to create the right abstractions and stay within the safe, declarative side of things.

The reality is, though, that crafting these building blocks takes time and thought, so we don't have them all yet. But people in the community have created fx/sub libraries that are already available, see e.g. this one: https://github.com/okwolf/hyperapp-fx that

Cheers!




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: