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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Hyperapp 2.0 #672

Closed
jorgebucaran opened this Issue Apr 1, 2018 · 160 comments

Comments

@jorgebucaran
Owner

jorgebucaran commented Apr 1, 2018

Note: This is not an April Fool's Day prank! 馃槃

Background

After a lot of debate and enduring weeks of inner turmoil, I've decided to move forward with a series of drastic changes that will be eventually released as Hyperapp 2.0.

Predictable as it may be, my plan was to leave things the way they are here and create a new JavaScript framework. A direct competitor to Hyperapp, but pretty much the same under the hood. I am calling that off and going to focus on making a better Hyperapp instead!

Breaking changes again? I am afraid so. I'm unsatisfied with how some things work in Hyperapp and want to fix them to the bone, not start a brand new project. I don't think I will be able to fairly concentrate on two projects that have exactly the same goal when what differentiates them are just some subtle (but important) API differences.

What will change?

With 2.0 I intend to fix several pain points I've experienced with Hyperapp due to its extreme DIY-ness. In the words of @okwolf:

Hyperapp holds firm on the functional programming front when managing your state, but takes a pragmatic approach to allowing for side effects, asynchronous actions, and DOM manipulations.

Hyperapp is minimal and pragmatic and I don't want to change it but in order to improve, we need to do more for the user. So, this is what I am going to focus on:

  • Easy to test 鈥 By making actions and effects pure, testing will be a piece of cake.
  • All Things Dynamic 鈥 First class support for code splitting and dynamically loading actions and views using import(), e.g., dynamic(import("./future-component")). Fix #533.
  • Cleaner Action API 鈥 Eliminate the confusing concept of "wired actions". The new actions will be regular, unwired & untapped JavaScript functions.
  • Subscriptions 鈥 Introduce a subscriptions API inspired by Elm.
  • Make Hyperapp more suitable for multi-app design (running multiple apps on a single page).
  • Types 鈥 Make Hyperapp typing simpler and easier to get right.

Show me the money!

The Simple Counter

Let's begin with a simple counter and kick it up a notch afterward.

import { h, app } from "hyperapp"

const down = state => ({ count: state.count - 1 })
const up = state => ({ count: state.count + 1 })

app({
  init: { count: 0 },
  view: state => (
    <div>
      <h1>{state.count}</h1>
      <button onclick={down}>-1</button>
      <button onclick={up}>+1</button>
    </div>
  ),
  container: document.body
})

The biggest surprise here is that you no longer need to pass the actions to the app() call, wait for them to be wired to state changes and receive them inside the view or any of that wacky stuff. It just works.

How to pass data into the action? Just use JavaScript.

You can create a closure that receives the data and returns a function that HA expects num => state => ({ count: state.count + num }) or use the tuple syntax (preferable) as shown below.

const downBy = (state, num) => ({ count: state.count - num })

const view = state => (
  <div>
    <h1>{state.count}</h1>
    <button onclick={down}>-</button>
    <button onclick={up}>+1</button>
    <button onclick={[downBy, 10]}>-10</button>
  </div>
)

This looks very similar to 1.0, the difference is that the curried function is completely justified now 鈥 not built-in or forced upon you.

Here's an interesting way you will be able to reset the count.

const view = state => (
  <div>
    <h1>{state.count}</h1>
    <button onclick={{ count: 0 }}>Reset</button>
    <button onclick={down}>-1</button>
    <button onclick={up}>+1</button>
  </div>
)

Yes, you just put the value { count: 0 } there and you're done.

Side Effects

Ok, time to cut to the chase. How are going to do async stuff now?

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

const up = state => ({ count: state.count + 1 })

// This is how we define an effect (Elm calls it commands).
const delayedUp = delay(1000, up)

app({
  init: { count: 0 },
  view: state => (
    <div>
      <h1>{state.count}</h1>
      <button onclick={up}>+1</button>
      <button onclick={delayedUp}>+1 with delay</button>
    </div>
  ),
  container: document.body
})

What if I want to set the state to something and cause a side effect to happen at the same time? I have you covered (almost). In Elm, they have tuples, but in JavaScript we only have arrays. So, let's use them.

const down = state => ({ count: state.count - 1 })
const up = state => ({ count: state.count + 1 })
const delayedUp = delay(1000, up)
const eventuallyDidNothing = state => [down(state), delayedUp]

Notice that creating a function for actions and effects is A Good Practice, but nothing prevents you from doing this:

const eventuallyDidNothing = state => [
  { count: state.count - 1 },
  delay(1000, state => ({
    count: state.count + 1
  }))
]

What about a full example that fetches some information on initialization? The following example is ported from Hyperapp + Hyperapp FX here.

import { h, app } from "hyperapp"
import { http } from "@hyperapp/fx"
import { url } from "./utils"

const quoteFetched = (state, [{ content }]) => ({ quote: content })
const getNewQuote = http(url, quoteFetched)

app({
  init: () => [{ quote: "Loading..." }, getNewQuote],
  view: state => <h1 onclick={getNewQuote} innerHTML={state.quote} />,
  container: document.body
})

Handling data from DOM events?

DOM events, like effects, produce a result or have data associated with them (http fetch response, DOM event, etc).

const textChanged = (state, event) => ({ text: event.target.value })

app({
  init: { text: "Hello!" },
  view: state => (
    <main>
      <h1>{state.text}</h1>
      <input value={state.text} oninput={textChanged} />
    </main>
  )
})

Interoperability

Absolutely. The app function will now return a dispatch function (instead of "wired actions") so you can execute actions or effects at will.

const { dispatch } = app(...)

// And then later from another app or program...

dispatch(action)
// or
dispatch(effect) // Time.out, Http.fetch, etc.

Dynamic Imports

What about dynamic imported components out of the box?

import { h, app, dynamic } from "hyperapp"

const Hello = dynamic({
  loader: () => import("./Hello.js"),
  loading: <h1>Loading...</h1>
})

app({
  init: { name: "Bender" },
  view: state => <Hello name={state.name} />,
  container: document.body
})

Subscriptions

There's one more important new feature: Subscriptions. Aptly ripped off Elm's subscriptions, this is how we are going to listen for external input now.

import { h, app } from "hyperapp"
import { Mouse } from "@hyperapp/subscriptions" // I am open to a shorter name.

const positionChanged = (state, mouse) => ({ x: mouse.x, y: mouse.y })

const main = app({
  init: {
    x: 0,
    y: 0
  },
  view: state => `${state.x}, ${state.y}`,
  subscriptions: state => [Mouse.move(positionChanged)],
  container: document.body
})

What's breaking?

  • Slices will be gone. There will be other mechanism to easily update props deeply nested, but other than that bye bye slices.
  • Obviously, actions that cause side effects will need to be upgraded to use managed FX, which should be a joy 鈥 you'll love FX. I don't believe this will be a particular difficult feat, but we'll see. For 1.0 migration help and support I've created a #1to2 channel in Slack, please join! 馃帀

Other

  • Middleware

  • Some things are still undecided. Should Hyperapp export all side effects? Perhaps we can reuse @hyperapp/fx for that?

  • I am going to remove slices! But now that actions are decoupled from the state update mechanism, we should be able to come up with similar patterns doing something similar to Redux's combineReducers.

  • Actions return a new state (not a partial state), so you need to merge the current state with whatever you return, similar to redux.

  • Lifecycle events? I am still not sure how to handle these. For now, I'll keep them the way they are.

  • Bundle size? All the core stuff is, in fact, less than current Hyperapp, but adding effects to that will makes us closer to 2 kB.

  • My interests have shifted to care more code readability and less about code golfing. A tiny code base is still a big priority for me, but not at the expense of making the code unreadable.

  • Server side rendering? I'm torn on this one. Should it be available out of the box or should we continue using @hyperapp/render? I am going to need @frenzzy to chime on this one.

  • JSX, @hyperapp/html, hyperx, h. Nothing is changing about here.

  • The router will continue to be an external module.

  • I'm still working hard to improve the performance and 2.0 will not affect #499 or #663. I suspect 2.0 will have slightly better performance out of the box because of how DOM events are going to be handled now.

When is this going to be available?

Very soon. I'll be pushing all the stuff to the fringe branch and publish as hyperapp@2.0-beta as soon as I can so we all can start playing with this.

Related

@icylace

This comment has been minimized.

icylace commented Apr 1, 2018

Sounds exciting !

However, in the code example for the "Dynamic Imports" section you use const Hello twice, one right after the other. You meant to have named them differently, right ?

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Apr 1, 2018

@icylace Yes, that's mistake and I just fixed it. Thanks for spotting it! 馃檱

@okwolf

This comment has been minimized.

Contributor

okwolf commented Apr 1, 2018

Will there be a way to provide your own custom managed fx? I'd also like to see a managed fx version of lifecycle events and ideally import too (if that's even possible). I really like the inspiration this takes from @hyperapp/fx 馃槉 馃攲

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Apr 1, 2018

Will there be a way to provide your own custom managed fx?

Yes.

I'd also like to see a managed fx version of lifecycle events

I need your help here! 馃槈

...ideally import too

Me too! But we're going to need a webpack/parcel guru to help with it; because of how they work by default.

I really like the inspiration this takes from @hyperapp/fx

Absolutely. 馃挴

@okwolf

This comment has been minimized.

Contributor

okwolf commented Apr 1, 2018

@jorgebucaran I need your help here! 馃槈

Executing existing actions/fx from lifecycle events should be trivial to support, this is already handled by @hyperapp/fx/fxapp.

To make the DOM element available for interop like in the existing oncreate /onupdate /ondestroy lifecycle events, we could provide that as part of the data given to that action. Or if we'd rather keep side effects quarantined to be only within fx, we could require writing a custom effect in order to perform direct DOM manipulations (this is more the mantra of fxapp).

@rbiggs

This comment has been minimized.

Contributor

rbiggs commented Apr 1, 2018

So where does this leave Ultradom? EOL?

@rbiggs

This comment has been minimized.

Contributor

rbiggs commented Apr 1, 2018

By the way, cudos on the rad redesign plan. Sounds like Hyperapp going into overdrive. 馃槈

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Apr 1, 2018

@rbiggs Good question!

So where does this leave Ultradom? EOL?

Fine. In good standing. I can maintain two similar projects. Now three was too many.


Having said that, we can continue exploring using ultradom in HA 2.0. A few things I want to add: the new HA will be purer, and this may drastically change how lifecycle events are used 鈥 also components are going to be lazy by default (a good thing!) and I want to see how much we can improve areas like inline CSS, CSS in JS, concatenating classes using classList (instead of string concatenation), etc.

Some of these changes should make it to ultradom anyway, but maybe some will be "conceptually" incompatible with it.

@rbiggs

This comment has been minimized.

Contributor

rbiggs commented Apr 1, 2018

Sound good to me. By the way, about inline CSS. You could support string and object values with just a check like this:

function updateAttribute(element, name, value, oldValue, isSvg) {
...
   } else if (name === "style" && typeof value !== 'string') {

That would let you have your cake and eat it to 馃嵃馃嵈You could then use normal inline CSS or a JavaScript object in case you need to make some style calculations based on a dynamic value. Not being able to using string-based inline styles has been a major pain point for me because I use a lot of SVG images. These invariably have tons of inline styles scatter around in all their various parts. The first time I used a bunch of SVG images with Hyperapp I spent hours trying to figure out why they weren't rendering. And then quite a bit of time digging through their code to convert the inline styles into JavaScript objects. It's so much easier to just support both. Please? All it requires is the above type check to have both ways of supporting inline CSS styles.

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Apr 1, 2018

@rbiggs Definitely. Just a simple node.style.cssText = value and boom.

@developerdizzle

This comment has been minimized.

developerdizzle commented Apr 1, 2018

Wow, hyperapp is evolving 馃槃

Do Time, Http, and Mouse need to be part of hyperapp? It seems odd to me to have that kind of functionality dependent on the library.

Also, would you mind elaborating on the decision to remove the actions parameter and add a dispatch method? Is this mainly to allow dynamic actions?

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Apr 1, 2018

@developerdizzle Do Time, Http, and Mouse need to be part of hyperapp? It seems odd to me to have that kind of functionality dependent on the library.

No, they definitely don't. I'll update the examples to better reflect what I have in mind.

Also, would you mind elaborating on the decision to remove the actions parameter and add a dispatch method? Is this mainly to allow dynamic actions?

It seems you misunderstood dispatch. I am not removing actions for a dispatch method. In fact, dispatch is only mentioned in the interop section above. The dispatch function will be the raw way to interop with Hyperapp from another program, e.g., a legacy Backbone app.

  • In 1.0, wired actions serve two functions: (1) interop and (2) subscriptions.
  • In 2.0 dispatch will serve only one function: interop.
@developerdizzle

This comment has been minimized.

developerdizzle commented Apr 1, 2018

I'll update the examples to better reflect what I have in mind.

That would be much appreciated!

It seems you misunderstood dispatch. I am not removing actions for a dispatch method. In fact, dispatch is only mentioned in the interop section above. The dispatch function will be the raw way to interop with Hyperapp from another program, e.g., a legacy Backbone app.

I think I do understand, but feel free to correct me here:

To provide actions - in 1.0 you have the actions parameter of the app function; in 2.0 you simply call the functions that would be properties of actions.

To call actions externally - in 1.0 the app function returns an object with methods we can call from other programs; in 2.0 we have to use dispatch to accomplish the same thing.

Basically the code goes from:

const actions = {
  up: state => ({ count: state.count + 1 }),
};

const myApp = app(state, actions, view, document.body);

myApp.up();

to

const up = state => ({ count: state.count + 1 });

const myApp = app(state, view, document.body); // Or is this now all one parameter?

myApp.dispatch(up);

Without fully understanding the decision, my personal preference would be to continue using the 1.0 technique as I feel it's cleaner and doesn't introduce new API for other programs to use.

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Apr 1, 2018

@developerdizzle In 2.0, dispatch will only serve to interop, so to call functions externally you will definitely use dispatch as you showed above.

Now, because in 1.0 you often do interop (since there are no subscriptions) this sounds inconvenient, but in 2.0 you will rarely do interop, because there will be a subscription API.

For example, in 1.0 you would use interop to implement the following code. In 2.0 you don't need to.

import { h, app } from "hyperapp"
import Mouse from "@hyperapp/mouse"

const positionChanged = (state, mouse) => ({ x: mouse.x, y: mouse.y })

const main = app({
  init: {
    x: 0,
    y: 0
  },
  view: state => `${state.x}, ${state.y}`,
  subscriptions: state => [Mouse.move(positionChanged)],
  container: document.body
})
@bendiksolheim

This comment has been minimized.

bendiksolheim commented Apr 1, 2018

Actions return a new state (not a partial state), so you need to merge the current state with whatever you return, similar to redux.

Can I ask about the rationale behind this one? I really like the way this works now, it makes nested actions a lot less verbose than if I would have to merge myself in each action. I guess the current behaviour might be surprising if you haven鈥檛 read the docs carefully though.

EDIT: just to not come out as purely negative: I really enjoy using HyperApp and love the fact that it is under heavy development! The ideas you have for 2.0 are really interesting, so I am in fact looking forward to it ;)

@okwolf

This comment has been minimized.

Contributor

okwolf commented Apr 1, 2018

If custom fx are passed the current state tree then you could make a merge effect that can do merges as shallow or nested as your heart desires.

(this is how fxapp handles state updates)

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Apr 2, 2018

@bendiksolheim I am still open to debate those little details. One thing I want to remove in 2.0 is slices, and for that reason, a shallow merge is not really that helpful.

@rbiggs

This comment has been minimized.

Contributor

rbiggs commented Apr 2, 2018

I'm glad to see subscriptions coming back. I was sad to see them go way back when.

You mention the things you want to work on and the last item is types. Do you mean something like React's PropTypes?

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Apr 2, 2018

@rbiggs Fixed it. I am saying our current typings are that very good and the reason: slices and wired actions. With that gone, getting the types right (while at the same time keeping your sanity intact) will be achievable.

@bendiksolheim

This comment has been minimized.

bendiksolheim commented Apr 2, 2018

Not really sure what you mean about slices, @jorgebucaran , I am not completely into all the concepts of Hyperapp yet :)

But thinking about it a bit more, it makes sense to return the full state when actions no longer follow the same structure that the state does. I don鈥檛 see any obvious way around it, actually. Maybe an alternative is a helper function to easily update nested state? Something like a more powerful version of Object.assign. It would also make it easier for people to upgrade from 1.x to 2.0.

@mindplay-dk

This comment has been minimized.

mindplay-dk commented Apr 2, 2018

This all looks neat!

The main things still missing for me is reuse: components.

For example, what if it were possible to have managed, auto-mounted app instances as components, by not initially specifying a container?

const Clock = app({
  init: { time: new Date() },
  view: state => (
    ... 
  ) 
});

app({
  view: state => (
    <Counter time={ new Date(...) }/>
  ),
  container: document.body
});

Component attributes would merely override any state properties set by init.

Component reuse is by far the biggest missing thing missing for me in any of the micro frameworks - and the techniques I've seen where lifecycle hooks are being used to create and mount more apps is not user-friendly enough for productive daily use.

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Apr 2, 2018

@mindplay-dk Have you given up on the single state tree architecture? 馃馃槈

@mindplay-dk

This comment has been minimized.

mindplay-dk commented Apr 2, 2018

Have you given up on the single state tree architecture?

It doesn鈥檛 provide the convenience, modularity or encapsulation that components provide - all of those factors are critical for efficient daily authoring and reuse.

I still work with a single application state model for self-contained applications - but components, in practice, have internal state details that are of no relevance to the application; the application shouldn鈥檛 have to manage the life-cycle of a date-picker, for example, anymore than it has to manage the life-cycle of a regular DOM input.

(Custom Elements may address this need in the future, but we鈥檙e not quite there yet.)

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Apr 2, 2018

@mindplay-dk Your opinion and comments are very valuable to me but have you considered that maybe the problem is that you are too conditioned to think in terms of "reusable components"?

Component reuse is by far the biggest missing thing missing for me in any of the micro frameworks - and the techniques I've seen where lifecycle hooks are being used to create and mount more apps is not user-friendly enough for productive daily use.

I don't know of any other micro-frameworks embracing the single state tree architecture. Sounds like Hyperapp to me. I know I haven't properly solved this problem yet in 1.0, but I want to with 2.0.

I love this quote from @wintvelt:

When you delegate state management to a component, you will often find that you still need outside read/ write access to the delegated state, which destroys the benefit of delegation, and often makes things worse.

@frenzzy frenzzy referenced this issue Apr 2, 2018

Closed

Inline styles #675

@mindplay-dk

This comment was marked as outdated.

mindplay-dk commented Apr 2, 2018

maybe the problem is that you are too conditioned to think in terms of "reusable components"?

I was not conditioned - quite the contrary.

I was initially very open to the idea of a single state tree - it鈥檚 what I was planning and hoping for after learning about the SAM pattern and when I first discovered Hyperapp and Picodom.

It鈥檚 an appealing idea, because it appears simple. I just don鈥檛 find it to be practical in reality. Real world components do have internal private state that is of no interest to the consumer, and I don鈥檛 want to manage these details by hand. It鈥檚 verbose, error-prone and distracting - as a consumer of a date-picker, for example, navigation state while paging through months is of no interest to me; I only care about the selected date. There are countless examples like that.

When you delegate state management to a component, you will often find that you still need outside read/ write access to the delegated state, which destroys the benefit of delegation, and often makes things worse.

Yes, this is why React distinguishes between 鈥減rops鈥 for state that is relevant to the consumer, and 鈥渟tate鈥 which is private and internal to the component instance and of no interest to the consumer.

You should never need read access to a component鈥檚 state - that shouldn鈥檛 even be a thing, and won鈥檛 be, if your component is designed properly. Instead, as a component consumer, you have write-access to an event hook, and you receive a notification that e.g. a new date has been picked, and apply it to your own state.

It鈥檚 exactly the same way you use a regular text input element - it also technically has internal state, such as focus, cursor position, selection etc. which are of no direct interest to the consumer; but that state still needs to exist internally in the element instance.

If you think of the relationship you have with DOM elements (inputs in particular) then the relationship you should have with a component is completely similar.

I think it鈥檚 a common mistake to try to build parts of your application using components and trying to make them responsible for governing parts of the total application state - from my point of view, that鈥檚 just a mistake, and not what I鈥檓 trying to achieve with components. It鈥檚 literally no different from how I expect to use DOM inputs - the only difference is components provide additional functionality for which we don鈥檛 have a DOM element.

I don鈥檛 want to manage the internal state, creation and destruction of a date-picker, anymore than I want to manage the state, creation and destruction of DOM inputs - which is where your library comes in.

But the moment I need a reusable control like a date-picker, I鈥檓 right back where we started, having to manage every instance and it鈥檚 state manually.

So the problem is only half solved, in a sense - because the library itself just creates a new layer with exactly the same problems you had with the DOM. (I鈥檓 emphasizing this, because I believe that鈥檚 the core of the problem, and the real real reason components exist anywhere.)

Components are a way to address those same problems, consistent with the way we address the problems with DOM elements - it鈥檚 the same problem and therefore the same solution, just at a higher level.

@kumarabhishek

This comment has been minimized.

kumarabhishek commented Apr 2, 2018

@mindplay-dk Perfect. You spoke exactly what I am also facing and expect something smart and out of the box. I am with you to have some mechanism to promote abstraction via state full component as well as something simple to get the state management done right. I am just wondering if we think in direction of Mobx for state management of entire app and stateful component for abstraction of complex component like datepicker, etc. I just read this https://medium.com/@botverse/enjoying-mobx-jsx-and-virtual-dom-621dcc2a2bd5 and though of sharing. I am not sure how much relevant it is but it take care of one part of the problem. Refer this too https://github.com/francesco-strazzullo/mobx-virtual-dom-example. Its old repo just for the sake of discussion.

@tomparkp

This comment has been minimized.

tomparkp commented Apr 2, 2018

To share some input in the other direction - I like hyperapp because it's essentially a minimalist React + Redux in a single framework with less magic (no providers, connect functions, etc). If you remove the single state tree and add component state what would make hyperapp any different than React or VueJS? Why not just use those (or if you want something smaller Preact) instead?

I'm very interested in a lot of the 2.0 changes, but worry a little hyperapp will turn into just another React clone like Preact or Inferno.

@russoturisto

This comment has been minimized.

russoturisto commented Apr 22, 2018

I've been thinking about the local state problem and I think the following user-space plugin would get around rebuilding of virtual dom everytime:

export interface IWarpBubble {

	warp(
		BubbleConstructor: WarpBubbleConstructor,
		...constructorParams: any[]
	): WarpBubble;

}

export interface WarpBubbleConstructor {
	new(...args: any[]): WarpBubble;
}

export class BubbleMap extends WeakMap<any, any> {
}

export class WarpBubble implements IWarpBubble {

	childBubbleMap: BubbleMap | null = null;

	warp(
		BubbleConstructor: WarpBubbleConstructor,
		...constructorParams: any[]
	): WarpBubble {
		if (!this.childBubbleMap) {
			this.childBubbleMap = new BubbleMap();
		}

		if (!constructorParams.length) {
			throw new Error('Cannot engage new warp bubbles without keys');
		}

		let bubbleMap = this.childBubbleMap;

		for (let i = 0; i < constructorParams.length - 1; i++) {
			let key = constructorParams[i];

			let nextBubbleMap = bubbleMap.get(key);
			if (nextBubbleMap && nextBubbleMap instanceof Map) {
				bubbleMap = nextBubbleMap;
			} else {
				nextBubbleMap = new Map();
				bubbleMap.set(key, nextBubbleMap);
				bubbleMap = nextBubbleMap;
			}
		}

		const lastKey = constructorParams[constructorParams.length - 1];
		let childBubble = bubbleMap.get(lastKey);
		if (!childBubble) {
			childBubble = new BubbleConstructor(...constructorParams);
			bubbleMap.set(lastKey, childBubble);
		}

		setTimeout(() => {
			this.sweep();
		});

		return childBubble;
	}

}

Then it could be used like:

class RootWarpBubble extends WarpBubble {
//...
}

class ChildWarpBubble extends WarpBubble {

  constructor(
     private typeInScope,
     private index
  ) {}

//...

}

const root= new RootWarpBubble();

export const view = (state, actions) => (
  <div>
    <h1>Todo</h1>
    <ul>
      {state.todos.map(({ id, value, done }) => (
        <TodoItem
                bubble={root.warp(ChildWarpBubble, "TodoItem", id)}
                id={id}
                value={value}
                done={done}
                someDerivedValue={someDerivedValueSelector(state)}
                toggle={actions.toggle}
         />
      ))}
    </ul>
  </div>
)

It seems that OO approach would be appropriate for local state. In case this is useful I started a ts project for this (with more conservative gc since I don't know how aggressive vms are with WeakMap).

https://github.com/russoturisto/warp-bubbles

It compiles but unfortunately I'm too swamped to actually test it, but it seems like this should work just fine. It is a constrained but strait forward approach (I think). Hopefully this helps someone.

Thanks, :)

Repository owner locked and limited conversation to collaborators Apr 22, 2018

Repository owner unlocked this conversation Jun 7, 2018

@russoturisto

This comment has been minimized.

russoturisto commented Jun 9, 2018

I'm very fond of Redux Devtools (http://extension.remotedev.io/) - save's me lots of time when debugging.

So, here is yet another beginner question: Would 2.0 be able support a user space plugin (possibly though middleware) where I wrap each action and record a unique type for it (probably the name of the function being called)?

If I understand correctly, it would then be theoretically possible to hook the HA store into the Redux Devtools plugin (not unlike what ngrx guys did - https://github.com/ngrx/platform/blob/master/docs/store-devtools/README.md).

Thanks, :)

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Jun 9, 2018

@russoturisto You are looking for #120! :) tl;dr: Yes. We just need to decide what the middleware API is going to look like.

@russoturisto

This comment has been minimized.

russoturisto commented Jun 9, 2018

Awesome, thank you! :)

@okwolf

This comment has been minimized.

Contributor

okwolf commented Jun 11, 2018

On a related devtools topic, will it be possible to inspect/manipulate the VDOM from the browser like the React devtools in 2.0? I guess we would need to provide an API for this or attach the vnode data to the actual DOM nodes. This could become a dev build only thing. (See #417)

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Jun 11, 2018

@okwolf Interesting. DOM nodes will play a bigger role in a future patch rewrite #499, so we should revisit this then.

@emil14

This comment has been minimized.

emil14 commented Jun 19, 2018

Am I the only one who think that this onclick={{ count: 0 }} is weird?

I haven't used Hyperapp yet, but I have some experience in React/Vue/Angular(1) and to me it doesn't looks like a good idea - onclick prop should take a function, right? Why it takes an object?

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Jun 20, 2018

EDIT: Chances are this little feature will not make it to 2.0, two reasons why:

  1. How do we get the name of the action while debugging? A variable is just a reference to an object, and the same object can be referenced by multiple variables. Functions have .name which allow us to find out the name of the action, but not objects.
  2. We need the state to calculate the next state, e.g., { ...state, count: 0} if we decide to merge by replacing instead of shallow merging.

@emil14 Actions are usually a function, but when I wrote that example I was thinking they could be an object too. I'm still not sure whether that feature will make it to the 2.0 cut or not, but I'll explain what I had in mind when I originally wrote it.

For example:

const reset = { count: 0 }
const increment = state => ({ count: state.count + 1 })

then use them like so:

<button onclick={reset}>Reset</button>
<button onclick={increment}>Increment</button>

Let's see another example. In the typical ToDo list app you usually have a way to filter items by completion, todo, etc.

import { filter } from "./actions"

//...

<div>
  {Object.keys(Filters)
    .filter(key => Filters[key] !== state.filter)
    .map(key => (
      <a href="#" onclick={actions.filter({ value: Filters[key] })}>
        {key}
      </a>
    ))}
</div>

The relevant part is the onclick handler:

onclick={filter({
  value: Filters[key]
})}

filter just needs to set a flag in the state. It doesn't need the entire state. Therefore it's defined simply as:

const filter = ({ filter }) => ({ filter })

API details are still subject to change as I finish wrapping 2.0. So take this with a grain of salt! 馃寠

@etylsarin

This comment has been minimized.

etylsarin commented Jun 26, 2018

Hi @jorgebucaran,
I was just about starting a new project utilizing the Hyperapp framework as our company standard. But now, when I read this thread, I'm getting a bit nervous. Do you have any timetable for 2.0? Would be better to start with all the benefits you described out of the box.
Anyway, thank you so much for running this awesome project!

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Jun 26, 2018

Hey @etylsarin! Thank you for your interest. I'm releasing the 2.0 alpha on the first week of July! :)

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Jul 9, 2018

#726 馃帀

@jorgebucaran

This comment has been minimized.

Owner

jorgebucaran commented Aug 31, 2018

TL;DR

I've created a series of issues to document the decision process behind the upcoming V2 API changes rendering this issue now obsolete.

Thanks to everyone who contributed their feedback. If you stumbled upon this issue today, refer to the ones below to get the most up-to-date and accurate information, otherwise enjoy the discussion above!

聽聽 馃憠 V2 Branch #726
聽聽 馃憠 Actions #749
聽聽 馃憠 Effects #750
聽聽 馃憠 Subscriptions #752
聽聽 馃憠 Middleware #753
聽聽 馃憠 Lazy Lists #721

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.