question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[RFC] Components & Mixin Guidelines

See original GitHub issue

Mixins and components are not the same thing!

We use components to describe parts of our app, like a TabBar, a ToggleButton, SlideButton, etc., and mixins to extend the state, actions and events of our app.

To extend means that we will merge your mixins state/actions/events with your app state/actions/events to create a single state/actions/events.

But often you need to pass actions to your component via props.

CodePen

function ToggleButton({ toggle, isOn }) {
  return ( < /* ... */ > )
}

app({
  actions: {
    toggle(state) {
      return { isOn: !state.isOn }
    }
  },
  view(state, actions) {
    return <ToggleButton toggle={actions.toggle} isOn={state.isOn} />
  }
})

This makes components, in a way, really “hollow” as we must teach it pretty much everything so it can thrive in the world. This is good, as it makes components extremely easy to test and debug. But what if the implementation of those actions is provided by a mixin?

In that case I think the mixin and component should be tucked away into their own module.

A good example of this is our friendly Router’s Link component.

import { router, Link } from "@hyperapp/router"

app({
  view: [
    [
      "/",
      (state, actions) =>
        <Link to="/test" go={actions.router.go}>
          Test
        </Link>
    ],
    [
      "/test",
      (state, actions) =>
        <Link to="/" go={actions.router.go}>
          Back
        </Link>
    ]
  ],
  mixins: [router()]
})

Notice how we must teach Link actions.router.go, which is fed to our actions by the router() mixin. I mean, it’s not so bad right? Just a little repetition is not so bad and this makes the Link component easy to test and debug and all that, right?

Sure! but the Link component would be just as easy to test and debug and all that if it shipped with actions.router.go already “pre-wired”… as long as you can overwrite the action and pass it a new one, just like it’s done above.

There is a PR here addressing just this. The idea is to be able to rewrite the example above as follows:

import { h, app } from "hyperapp"
import { router } from "@hyperapp/router"

app({
  view: (state, actions, { Route }) => <Route />,
  mixins: [
    router([
      [
        "/", (state, actions, { Link }) =>
          <Link to="/test">
            Test
          </Link>
      ],
      [
        "/test", (state, actions, { Link }) =>
          <Link to="/">
            Back
          </Link>
      ]
    ])
  ]
})

Notice now we don’t need to teach the Link actions.router.go anymore! 💯

On the other hand, see that we now need to grab the pre-wired Link component somehow, since:

import { Link } from "@hyperapp/router"

…would take us back to square one (wouldn’t work unless we pass actions.router.go ourselves).

The benefit is less boilerplate, but it comes at a cost: we need to overload the view function with a third argument! 🤔

The good news is that none of this requires changes in core! 🎉

This is already possible using events.render.

But the real question to me is which way is more expensive? Overloading the view function or having to manually wire components every time we use them?

The only caveat about overloading the view function is you need to make sure not to break other authors also overloading the function. This is already solved, of course, using “namespaces”, which fortunately we already support.

actions: {
  myNamespace: {
  // My actions
  },
  yourNamespace: {
  // Your actions
  },
  ...
}

Now, while the Router and Link component are good examples to introduce this idea, we could easily shrug it off and get away without it just as we have done until now.

IMO more complex components will benefit from this pattern. Imagine a rich text editor in Hyperapp.

It could look as follows:

import { h, app } from "hyperapp"
import { editor } from "./editor"

app({
  view(state, actions, { editor: Editor }) {
    return <Editor />
  },
  mixins: [editor({/*...*/})]
})

Compare how it might look like without overloading the view function:

import { h, app } from "hyperapp"
import { Editor, editor } from "hyperapp-rt-editor"

app({
  view: (state, actions) => (
      <Editor state={state.editor} actions={actions.editor} />
  ),
  mixins: [editor({/* ... */})]
})

Not the worst, but I prefer the previous pattern. What about you?

Whichever style you end up choosing for your own components+mixins is ultimately up to you.

👋

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:16 (13 by maintainers)

github_iconTop GitHub Comments

8reactions
lukejacksonncommented, Sep 15, 2017

Much prefer <Editor /> over <Editor state={state.editor} actions={actions.editor} />

7reactions
zacenocommented, Sep 15, 2017

👍 for view(state, actions, {editor: Editor}) {...}

Read more comments on GitHub >

github_iconTop Results From Across the Web

Change: Don't use Vue Mixins (#32) · Issues - GitLab
Many of our mixins are dependent on certain properties existing on the component, without making that dependency explicit. e.g ...
Read more >
rfcs/0671-modernize-built-in-components-1.md at master
Deprecate calling reopen or reopenClass on the classes and mixins listed above, whether they were obtained through an import or the Ember ...
Read more >
The Mixin Pattern In TypeScript – All You Need To Know Part 2
The mixin class​​ Now let's define what is a “requirement”. Requirements can be of two types: Another mixin class, which needs to be...
Read more >
rfc:mixin - PHP.net wiki
The mixin clobbers any methods or properties into the given object. It does not replace any exiting methods or properties defined in the...
Read more >
Vue 3 - The Composition API - Reusability (Part 2) - Fullstack.io
Vue merges the properties of every mixin added to a component's mixin option with its existing options.Example: ( src/mixin.js ).
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found