These are chat archives for mithriljs/mithril.js

21st
Jul 2019
Oscar
@osban
Jul 21 00:04 UTC
@ianjosephwilson :+1:
James Forbes
@JAForbes
Jul 21 02:27 UTC

Wow great read this morning πŸ˜€ @orbitbot @spacejack @CreaturesInUnitards @barneycarroll @gilbert. I've got a lot of thoughts!

@gilbert

Could mithril provide a singleton stream to simulate the global nature of react hooks?

Yeah I think so.

I don't know where I proposed it, but it was prior to the release of v1 I was suggesting we ship streams in core and replace lifecycle methods for streams that are passed in by mithril into the component. I think it was due to my scepticism of the v1 routing api.


function Example({ streams: { update, create, remove } }){
  onremove.map( vnode => /* do stuff */ )

  m.stream.merge([update, create]).map(
    () => /* do stuff on update _and_ create */
  )
}

If you had that, it's a big improvement in ergonomics for combining custom streams with mithril lifecycle state.

I also think it'd be great if there was a stream constructor that accepted a kill stream as an argument. Flyd had something similar. Then you could limit the lifetime of a stream to be the same as a component as easily as:

const delay = m.stream.create(1000, vnode.streams.remove)

But also easily end streams when arbitrary state changes happen:

// Count stream ends when component is removed
const count = m.stream.create(0, vnode.streams.remove)

// bigCount emits when count is > 100
const bigCount = count.filter( x => x > 100 )

// delay is ended when count > 100 or when component is removed
const delay = m.stream.create(
  1000
  , bigCount
)

Then all the hook like composition just happens with streams in the closure and the view just uses existing state and triggers updates to that state by writing to the streams. I built the lifetime scoping into my routing layer a long time ago, and it works great.

I create a stream like so in my v0.2x closure component

function MyComponent({ scoped }){
  var count = scoped(0) // ends when component ends

  return () => m('h1', count())
}

The router automatically kills the streams on route change, not component unmount, which isn't as good but was easier to do globally.

I think it's really great that the new svelte has basically mithril streams (in their stores) and people are going "that's a viable alternative to hooks".

E.g. this twitter thread: https://twitter.com/AdamRackis/status/1126689567975677953

I think this sort of stuff is a great approach but will probably be always to radical to introduce into core. I think streams are so generally useful we could lean on them for more core behaviour in mithril (e.g. routing).

It'd be cool if mithril had some global hooks to add this sort of custom experimental behaviour to the vnode without having to actually alter the core framework.

e.g.

m.plugin({
  component: {
    oncreate: vnode => vnode, // add vnode.streams
    onremove: vnode => vnode // end scoped 
    onerror: ...
  },
  route: {
    onchange: ...
  },
})

But as to having non rendering behavior in the view, I don't know, I just think one just need to think of the "view" more broadly. It's really the boundary between pure state transforms and IO/side effects.

So it's pretty logical to me to have an object that manages side effects be returned from a function so it can be interpreted and executed externally. That's sort of what the free monad/interpreter pattern is. Having components without view methods makes a lot of sense in that context.

I'd rather have side effects at the boundary of the component instead of peppered across life cycle methods. That's kind of the benefit of react hooks, but it's also a compelling reason to have components without view methods.

Scotty Simpson
@CreaturesInUnitards
Jul 21 03:13 UTC
@JAForbes outstanding as always. I have 2 cents:
Scotty Simpson
@CreaturesInUnitards
Jul 21 03:20 UTC
I agree with @spacejack and others that non-rendering behavior in the view is weird, but I also agree with you that It's not much of a leap to simply, as you say, "think of the 'view' more broadly". I'm curious to hear the arguments against view || onremove for component validation checking...
In any case, patterns are one thing, but discussions about adding API surface make me grumpy. Especially when the current idioms can accommodate the proposed functionality without much bother.
Scotty Simpson
@CreaturesInUnitards
Jul 21 03:27 UTC
So that's the crux of why I'm all "get off my lawn" about hooks in Mithril, especially in light of the elimination of classes. There's essentially a single, 2-branch idiom for what's becoming more MVVM every day (if you'll excuse the use of a soooo 2015 expression). I think it's better to further flex the existing idiom's capabilities than to add surface.
James Forbes
@JAForbes
Jul 21 04:53 UTC

Yeah I agree with you @CreaturesInUnitards. Ideally I'd like to see a mithril that eschews api surface area by leaning on streams. I think there's a lot that mithril offers that is basically a stream but isn't presented as such. I'm very much against expanding surface area.

But I'm also saying, it's totally fine to keep the existing API and accept that components can be used as a hook like paradigm. Part of what makes that awkward in React is that sharing data across side effecting sibling components is complicated (callbacks). But streams let two components communicate without either knowing about eachother, so there's less of a need to add a new abstraction into the mix.

That's basically what my "declarative setInterval" was demonstrating, mithril is fine as it is

Scotty Simpson
@CreaturesInUnitards
Jul 21 04:55 UTC
@JAForbes :100:
so in that vein... what would you think about viewless components, which simply mandate that onremovebe present?
Patrik Johnson
@orbitbot
Jul 21 05:07 UTC
err, what would be the point of that?
what I'm currently not grokking in this discussion is how a different way of attaching your callbacks to whatever is going on is that significantly better/worse than what the current implementation is
as such, if someone wants to kick an implementation like this around, AFAICT it's pretty easy to write a base factory / class to provide streams to make the argument clearer
Scotty Simpson
@CreaturesInUnitards
Jul 21 05:30 UTC
@orbitbot it's about intent, in my head, but I readily admit that the idea is less than half-baked. I'm tinkering.
Patrik Johnson
@orbitbot
Jul 21 05:34 UTC
yeah, I guess I'm kind of getting similar feels. I just haven't really seen any IMO compelling reasons to change/complement the API to something I currently feel is (to a new user) less intuitive, to afford / promote a separate way for achieving the same end results
so far it's pretty much a coding style / paradigm argument, I feel
Scotty Simpson
@CreaturesInUnitards
Jul 21 05:48 UTC

I think my only disagreement with that is that "less intuitive" is context-dependent. If I have a "value-only" component, which doesn't contribute anything to the view, I'm not convinced that it's intuitive to say "have the view just pass through the children" or "have the view return null". I think saying "just include onremove and clean up there" may be compelling.

It also may not, and you may be right. I'm pretty drunk a lot of the time :stuck_out_tongue_winking_eye:

James Forbes
@JAForbes
Jul 21 06:04 UTC

@orbitbot

how a different way of attaching your callbacks to whatever is going on is that significantly better/worse than what the current implementation is

I mean, callbacks aren't equivalent to what streams. Callbacks don't compose easily because they don't return anything. Callbacks are just, when that happens do this. Streams are a value that represents that effect for all points in time. It's something you can pass around to functions, it's shareable therefore. It's composeable therefore.

Composing callbacks is possible, but it's really hard to do. It's awkward. And in order to do it well you just end up inventing streams.

To compose mithril lifecycle hooks you likely need to compose components. And the argument is, why is that necessary? The value proposition of both streams and react hooks is that you can compose effects without introducing a new component boundary or by mixing unrelated effects together.

@CreaturesInUnitards

what would you think about viewless components, which simply mandate that onremove be present

I don't know honestly. I just know I feel comfortable doing view: () => null πŸ˜€

Patrik Johnson
@orbitbot
Jul 21 06:32 UTC
I was referring to where you write your handlers, not that callbacks would be equal to streams
I guess philosophically I view most of the components I write as pretty much atomic, and perhaps most of the things I've written in mithril aren't CRUD enough to really see the point in abstracting away all that much, other than the occasional helper
James Forbes
@JAForbes
Jul 21 07:38 UTC
ah ok sorry, yeah I get that
Zunix
@decimocracy
Jul 21 12:32 UTC
thanks @spacejack
Zunix
@decimocracy
Jul 21 13:32 UTC
Is there any simple way to directly render elements via components.
like returning <div>stuff</stuff>?
I have some generated svg
and I cannot just convert it to hyperscript
Scotty Simpson
@CreaturesInUnitards
Jul 21 14:10 UTC
Maybe I misunderstand the question, but I think you’re looking for m.trust(svgStr)
Zunix
@decimocracy
Jul 21 14:11 UTC
Hey!!!
That should work
Oscar
@osban
Jul 21 14:17 UTC
@decimocracy otherwise you can try to use m('svg') directly
Zunix
@decimocracy
Jul 21 14:44 UTC
and I'll still have to use m.trust for children. Right?
AHa!
this thing exists too
Oscar
@osban
Jul 21 15:11 UTC
you only have to use m.trustif you can't solve it with normal tags
Scotty Simpson
@CreaturesInUnitards
Jul 21 15:49 UTC

and I cannot just convert it to hyperscript

This threw me, I guess 😏

Oscar
@osban
Jul 21 15:58 UTC
😢
Zunix
@decimocracy
Jul 21 16:43 UTC
@CreaturesInUnitards yes, you got it right.
Oscar
@osban
Jul 21 17:04 UTC
@decimocracy svg example
Scotty Simpson
@CreaturesInUnitards
Jul 21 17:32 UTC
πŸ‘
Zunix
@decimocracy
Jul 21 18:28 UTC
As I said SVG is generated. But, I found the mithril template converter useful. Though, Thanks you guys.
Also, is there any suggestion from Mithril for state management architecture?
Besides POJO
I am trying to keep data uni-directional but some state change/data will do bi-directional.
And I want to make that really explicit.
spacejack
@spacejack
Jul 21 18:34 UTC
@decimocracy two-way binding is explicit in Mithril. Example
spacejack
@spacejack
Jul 21 18:39 UTC
Or with a simple external actions, state module