Skip to content

Commit

Permalink
Fix docs bug, advise against reusing vnode.attrs itself [skip ci]
Browse files Browse the repository at this point in the history
  • Loading branch information
dead-claudia committed Oct 17, 2018
1 parent e7c71f6 commit ac62bc3
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 10 deletions.
2 changes: 1 addition & 1 deletion docs/change-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
- render/events: `Object.prototype` properties can no longer interfere with event listener calls.
- render/events: Event handlers, when set to literally `undefined` (or any non-function), are now correctly removed.
- render/hooks: fixed an ommission that caused `oninit` to be called unnecessarily in some cases [#1992](https://github.com/MithrilJS/mithril.js/issues/1992)
- docs: tweaks: ([#2104](https://github.com/MithrilJS/mithril.js/pull/2104) [@mikeyb](https://github.com/mikeyb), [#2205](https://github.com/MithrilJS/mithril.js/pull/2205), [@cavemansspa](https://github.com/cavemansspa))
- docs: tweaks: ([#2104](https://github.com/MithrilJS/mithril.js/pull/2104) [@mikeyb](https://github.com/mikeyb), [#2205](https://github.com/MithrilJS/mithril.js/pull/2205), [@cavemansspa](https://github.com/cavemansspa), [#2250](https://github.com/MithrilJS/mithril.js/pull/2250) [@isiahmeadows](https://github.com/isiahmeadows))
- render/core: avoid touching `Object.prototype.__proto__` setter with `key: "__proto__"` in certain situations ([#2251](https://github.com/MithrilJS/mithril.js/pull/2251))

---
Expand Down
107 changes: 98 additions & 9 deletions docs/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,34 +363,123 @@ This way, the `Auth` module is now the source of truth for auth-related state, a

As a bonus, notice that we no longer need to use `.bind` to keep a reference to the state for the component's event handlers.

#### Avoid restrictive interfaces
#### Don't forward `vnode.attrs` itself to other vnodes

Try to keep component interfaces generic - using `attrs` and `children` directly - unless the component requires special logic to operate on input.

In the example below, the `button` configuration is severely limited: it does not support any events other than `onclick`, it's not styleable and it only accepts text as children (but not elements, fragments or trusted HTML).
Sometimes, you might want to keep an interface flexible by forwarding attributes to a particular child component or element:

```javascript
// AVOID
// Example
var RestrictiveComponent = {
view: function(vnode) {
return m("button", {onclick: vnode.attrs.onclick}, [
"Click to " + vnode.attrs.text
"Click to " + vnode.children
])
}
}

var FlexibleComponent = {
view: function(vnode) {
return m("button", vnode.attrs.attrs, [
"Click to ", vnode.children
])
}
}
```

If the required attributes are equivalent to generic DOM attributes, it's preferable to allow passing through parameters to a component's root node.
When doing so, it might be tempting to simplify `FlexibleComponent` to this, removing the intermediate `vnode.attrs.attrs` and `vnode.attrs.text` and forwarding `vnode.attrs` and `vnode.children` directly:

```javascript
```js
// AVOID
var FlexibleComponent = {
view: function(vnode) {
return m("button", vnode.attrs, [ // <= not okay
"Click to ", vnode.children
])
}
}

m(FlexibleComponent, {
oncreate: function(vnode) {
vnode.dom.ontransitionend = function() {
vnode.dom.classList.remove("fade-in")
vnode.dom.ontransitionend = null
}
vnode.dom.classList.add("fade-in")
},
onbeforeremove: function(vnode) {
// This gets called twice, both times awaited!
return new Promise(function(resolve, reject) {
vnode.dom.classList.remove("fade-in")
vnode.dom.classList.add("fade-out")
vnode.dom.ontransitionend = function() {
vnode.dom.classList.remove("fade-out")
vnode.dom.ontransitionend = null
resolve()
}
})
}
})
```

Keys also can fail to work as expected:

```js
// AVOID
var FlexibleButton = {
view: function(vnode) {
return [
m("button", vnode.attrs, [ // <= not okay
"Click to ", vnode.children
]),
m(".menu", menuEntries)
]
}
}

// You do this:
m(FlexibleButton, {}, "go here")

// Here's how Mithril's diffing algorithm reads that
return [
m("button", "Click to go here"),
m(".menu", menuEntries)
]

// You do this:
m(FlexibleButton, {key: "undefined"}, "go here")

// Here's how Mithril's diffing algorithm reads that
return [
// This is a problem because keys are compared as object properties when
// calculating positions, but compared via `===` when moving nodes, and
// Mithril assumes keys are always of the same type when doing so.
m("button", {key: "undefined"}, "Click to go here"),
m(".menu", {key: undefined}, menuEntries)
]
```

Instead, what you should do in these cases is use a dedicated attribute like `attrs`. So the above two problematic examples should be fixed to something like these three:

```js
// PREFER
var FlexibleComponent = {
view: function(vnode) {
return m("button", vnode.attrs, [
return m("button", vnode.attrs.attrs, [ // <= okay
"Click to ", vnode.children
])
}
}

var FlexibleButton = {
view: function(vnode) {
return [
m("button", vnode.attrs.attrs, [ // <= okay
"Click to ", vnode.children
]),
m(".menu", menuEntries)
]
}
}
```

#### Don't manipulate `children`
Expand Down

0 comments on commit ac62bc3

Please sign in to comment.