Do we have some examples or docs for the hyperappv2? I've found https://github.com/hyperapp/hyperapp/issues/672 Is it ok for learning how to ha2 works?
@dmitrykurmanov Nope, there are still no docs—HAV2 is not even out yet—but the V2 branch has most of the source code and you can try it out here. 👨💻
@jorgebucaran Could you please improve the example above? It would be great if you add a subscribe property to the app props.
I am not familiar with elm and elm's effects(commands). This is the hardest part of hyperapp2.0 to understand for me😊
Oh, I think If we pass just function to subscribe it will call on every action. Interesting... https://codepen.io/anon/pen/PBbvRg
and if I understand it correcly in first param of sucbscribe function we get new state. Looks usefull. 👍
Pen with effect: https://codepen.io/anon/pen/BPpjJY based on @SkaterDad 's comment
Is it ok to pass new state to effect props ? May be I've missed something)
PS: Should I post theese example into V2 pull request and close this issue?
@dmitrykurmanov In elm, the effects (aka. commands) are typically things like http requests, sending a message over websockets, or other "side effect" type things which don't have to resolve immediately.
Your code that enforces a maximum value would fit more easily inside of the up function, in my opinion. However, if you did need to trigger a side effect when that maximum is hit, you could return that effect.
const up = state => {
const newState = state + 1;
if (newState > 10) {
return [10, sendMaxValueReport] // could be sending an http request to a logging service
}
return newState;
}
Here's a very minimal HAV2 example showing a random number use case with fx: https://codepen.io/okwolf/pen/LBRmXj?editors=0010
@okwolf Nice!
I forked your pen and made it closer to what we'd do in Elm. We want actions that return a tuple of the next state and the effect, instead of attaching effects directly to DOM handlers.
https://codepen.io/anon/pen/ejvOvN?editors=0010
const Roll = state => [state, Random({ min: 1, max: 10, action: NewFace })]
const NewFace = (state, newFace) => ({ ...state, dieFace: Math.floor(newFace) })
app({
init: Roll({ dieFace: 1 }),
view: state => (
<div>
<h1>{state.dieFace}</h1>
<button onClick={Roll}>Roll</button>
</div>
),
container: document.body
})
@jorgebucaran thanks!
I had a few questions though:
dieFace of 1. This won't cause FOOC in this example, but imagine if the Roll action/command were delayed and animated to simulate the die rolling. Or perhaps there should initially be no value and require the user to roll to get one? The point is to avoid creating the appearance of having fairly rolled a 1 initially.@jorgebucaran also FYI this pen has both the HA 1.2.8 UMD build (Settings -> JavaScript -> Add External Scripts/Pens) and HA 2 module source included. Sometimes it won't load for me.
@okwolf
Possibly. A future development build of Hyperapp (or middleware-enabled logger/debugger) would be unable to tell what action is updating the state in your example.
You could do this then:
const Roll = state => [state, Random({ min: 1, max: 10, action: NewFace })]
const NewFace = (_, newFace) => Math.floor(newFace)
app({
init: Roll(1),
view: state => (
<div>
<h1>{state}</h1>
<button onClick={Roll}>Roll</button>
</div>
),
container: document.body
})
For the record, Roll(1) returns a tuple of
[1, Random(..)]which causes the state to be set to 1 and run the effect immediately after.
Here's an HTTP example pen to play around with: https://codepen.io/okwolf/pen/BPRmNM?editors=0010
One more example: this time using WebSockets to show live order prices for a crypto that shall remain nameless: https://codepen.io/okwolf/pen/ejRWZb?editors=0010
@okwolf Thanks for those examples. I like how this is shaping up.
I asked this question in the V2 PR, but will repeat here since your examples use effects. What is the reason they are coded like this?
const randomEffect = (props, dispatch) => {
const randomValue = Math.random() * (props.max - props.min) + props.min;
dispatch(props.action, randomValue)
};
export const Random = ({ action, min = 0, max = 1 }) => ({
props: {
action,
min,
max
},
effect: randomEffect
});
Instead of this?
export const Random = ({ action, min = 0, max = 1 }) => dispatch => {
const randomValue = Math.random() * (max - min) + min;
dispatch(action, randomValue)
};
With the current form, hyperapp's runtime has to do some gymnastics to pass the props to the effect handling function after checking that it is an effect. It would be simpler to just say: "hey, that 2nd array member is a function. i'll just give it the dispatch function and move on".
@SkaterDad I made it this way for easier testing. In your implementation, calling Random({ action: MyAction }) produces a new function every time, making it harder (if not impossible) to test.
Does that make sense? I'll prepare an example for you in a moment.
@SkaterDad it's even more important to define your effect separately for subscriptions since one of the things Hyperapp compares between subs is the effect function. If you defined that as an anonymous new function then you would be unsubscribing and resubscribing quite often, which is bad for performance.
@okwolf Ditto.
@SkaterDad If your example was a subscription (because the effect function is different every time we evaluate the subscription) we'd always resubscribe, making the whole deal about subscriptions pointless.
About testing effects, consider this app:
const Roll = state => [state, Random({ min: 1, max: 10, action: NewFace })]
const NewFace = (_, newFace) => Math.floor(newFace)
app({
init: 0,
view: state => (
<div>
<h1>{state}</h1>
<button onClick={Roll}>Roll</button>
</div>
),
container: document.body
})
How do we test our actions?
First import what we need:
import { Roll, NewFace } from "./actions"
Next, let's test NewFace, it's so simple it hurts.
NewFace(null, 7) === 7 // Ok!
What about Roll? Well, our developer is saying that Roll returns a random number between 1 and 10 and calls NewFace with the result. So, it looks we need to import the Random effect too.
import { deepEqual } from "assert"
import { Roll, NewFace } from "./actions"
import * as Random from "@hyperapp/random"
const [, effect] = Roll(null)
deepEqual(
effect,
Random.generate({
min: 1,
max: 10,
action: NewFace
})
)
or let's say you have your own assertions:
import { Roll, NewFace } from "./actions"
import { Random } from "@hyperapp/random"
import { testEffect } from "./test/util/testEffect"
testEffect(Roll, Random({ min: 1, max: 10, action: NewFace }))
This is more or less what V2 testing is going to look like. This is raw simplicity. Everything is sync. No mocks, no spies, no more weird stuff.
@jorgebucaran Thanks for the detailed explanation. The design makes a lot more sense now. :+1:
@jorgebucaran any idea why using JSX with HAV2 in a codepen is still causing this error: Uncaught SyntaxError: Unexpected token <?
@okwolf It's definitely a CodePen bug, because some of my old pens that were working are now suddenly broken. It only happens when using modules.
@jorgebucaran, @okwolf I've posted the pen with the problem to the codepen support. May be it will help.
For now I'm just happy to have an excuse not to use JSX :trollface:
FYI @hyperapp/html won't work in pens for HAv2, but hyperscript-helpers will.
I've made a codepen collection of my examples so far.
@okwolf Do you have an example of @hyperapp/html doesn't work? Do you mean type=module doesn't work?
@jorgebucaran I meant the @hyperapp/html UMD build as published expects to use the hyperapp global, but that only works if using a published Hyperapp UMD build instead of importing directly from GitHub. A better approach would be to not have @hyperapp/html depend on hyperapp at all but instead export a helper that you provide your h function with from wherever you get it, like how hyperscript-helpers works.
Thank you, @dmitrykurmanov for starting this discussion. V2 will include documentation when it's officially released! 🎉
Cheers!
Most helpful comment
@okwolf Ditto.
@SkaterDad If your example was a subscription (because the effect function is different every time we evaluate the subscription) we'd always resubscribe, making the whole deal about subscriptions pointless.
About testing effects, consider this app:
How do we test our actions?
First import what we need:
Next, let's test NewFace, it's so simple it hurts.
What about Roll? Well, our developer is saying that Roll returns a random number between 1 and 10 and calls NewFace with the result. So, it looks we need to import the Random effect too.
or let's say you have your own assertions:
This is more or less what V2 testing is going to look like. This is raw simplicity. Everything is sync. No mocks, no spies, no more weird stuff.