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

RFC: Hyperapp 2.0 #672

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

RFC: Hyperapp 2.0 #672

jorgebucaran opened this issue Apr 1, 2018 · 160 comments
Labels

Comments

@jorgebucaran
Copy link
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 Dynamic actions: How to add new actions at runtime? #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 Rewrite patch algo / improve diffing performance #499 or Keyed children diff optimization #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 [email protected] as soon as I can so we all can start playing with this.

Related

@icylace
Copy link
Contributor

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
Copy link
Owner Author

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

@okwolf
Copy link
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
Copy link
Owner Author

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
Copy link
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
Copy link
Contributor

rbiggs commented Apr 1, 2018

So where does this leave Ultradom? EOL?

@rbiggs
Copy link
Contributor

rbiggs commented Apr 1, 2018

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

@jorgebucaran
Copy link
Owner Author

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
Copy link
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
Copy link
Owner Author

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

@developerdizzle
Copy link

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
Copy link
Owner Author

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
Copy link

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
Copy link
Owner Author

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
Copy link

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’t 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
Copy link
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
Copy link
Owner Author

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
Copy link
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
Copy link
Owner Author

@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
Copy link

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’t 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
Copy link

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
Copy link
Owner Author

jorgebucaran commented Apr 2, 2018

@mindplay-dk Have you given up on the single state tree architecture? 🤔😉

@mindplay-dk
Copy link

Have you given up on the single state tree architecture?

It doesn’t 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’t 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’re not quite there yet.)

@jorgebucaran
Copy link
Owner Author

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 mentioned this issue Apr 2, 2018
@mindplay-dk

This comment has been minimized.

@kumarabhishek
Copy link

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
Copy link

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.

@okwolf
Copy link
Contributor

okwolf commented Apr 18, 2018

@jorgebucaran Essentially an action reducer (as opposed to a state reducer).

How about a dispatch reducer? That would give your enhancer freedom to inject logic before or after your original dispatch.

What do you think of something like this?

const logger = dispatch => action => {
  const getState = () => dispatch(state => state)
  console.log('before state: ', getState())
  console.log('action: ', action)
  dispatch(action)
  console.log('next state: ', getState())
}

app({
  enhancer: logger
})

@jorgebucaran
Copy link
Owner Author

@okwolf TBH I'm still not sure. I don't want to give users too much power, but I do want a way to "hook into" the dispatch function, so that you can, e.g., record actions for later replay.

@willin
Copy link

willin commented Apr 19, 2018

@jorgebucaran so you will not take part in fxapp?

@okwolf
Copy link
Contributor

okwolf commented Apr 20, 2018

@willin so you will not take part in fxapp?

@jorgebucaran has reviewed some of the work I've done on fxapp, which has served as a playground for trying out a few similar ideas, although closer to the original @hyperapp/fx. I still plan to keep the project around, and evolve it in response to the direction Hyperapp 2.0 is headed.

@artem-v-shamsutdinov
Copy link

artem-v-shamsutdinov 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
@jorgebucaran jorgebucaran mentioned this issue May 24, 2018
10 tasks
Repository owner unlocked this conversation Jun 7, 2018
@artem-v-shamsutdinov
Copy link

artem-v-shamsutdinov 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
Copy link
Owner Author

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

@artem-v-shamsutdinov
Copy link

Awesome, thank you! :)

@okwolf
Copy link
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
Copy link
Owner Author

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

@emil14
Copy link

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
Copy link
Owner Author

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
Copy link

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
Copy link
Owner Author

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
Copy link
Owner Author

#726 🎉

@jorgebucaran jorgebucaran added the outdated Forgotten lore label Aug 31, 2018
Repository owner locked and limited conversation to collaborators Aug 31, 2018
@jorgebucaran
Copy link
Owner Author

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.
Labels
Projects
None yet
Development

No branches or pull requests