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

Recast key docs to be much clearer and more accurate #2540

Merged
merged 2 commits into from
Oct 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/censor.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ function Layout() {
}
```

This would end up [throwing an error](keys.md#avoid-mixing-keyed-and-non-keyed-vnodes-in-the-same-array) because here's what Mithril sees when creating the `Layout` vnode:
This would end up [throwing an error](keys.md#key-restrictions) because here's what Mithril sees when creating the `Layout` vnode:

```javascript
return [
Expand Down
4 changes: 2 additions & 2 deletions docs/framework-comparison.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Framework comparison

- [Why not X?](#why-not-insert-favorite-framework-here)
- [Why use Mithril?](#why-use-mithril)
- [Why not X?](#why-not-insert-favorite-framework-here?)
- [Why use Mithril?](#why-use-mithril?)
- [React](#react)
- [Angular](#angular)
- [Vue](#vue)
Expand Down
2 changes: 1 addition & 1 deletion docs/hyperscript.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ m.render(document.body, userInputs(users))

Having a key means that if the `users` array is shuffled and the view is re-rendered, the inputs will be shuffled in the exact same order, so as to maintain correct focus and DOM state.

To learn more about keys, [see the keys page](keys.md)
To learn more about keys, [see the keys page](keys.md).

---

Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Introduction

- [What is Mithril?](#what-is-mithril)
- [What is Mithril?](#what-is-mithril?)
- [Getting started](#getting-started)
- [Hello world](#hello-world)
- [DOM elements](#dom-elements)
Expand Down
591 changes: 437 additions & 154 deletions docs/keys.md

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions docs/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<meta charset="UTF-8" />
<title>Mithril.js</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/themes/prism.min.css" rel="stylesheet" />
<link href="style.css" rel="stylesheet" />
<link rel="icon" type="image/png" sizes="32x32" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand All @@ -27,8 +28,9 @@ <h1><img src="logo.svg"> Mithril <small>[version]</small></h1>
<small>License: MIT. &copy; Leo Horie.</small>
</section>
</main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/prism.min.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/components/prism-jsx.min.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/components/prism-jsx.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/components/prism-diff.min.js"></script>
<script src="https://unpkg.com/mithril@[version]/mithril.js" async></script>
<script>
document.querySelector(".hamburger").onclick = function() {
Expand Down
6 changes: 3 additions & 3 deletions docs/route.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ Argument | Type | Description
`vnode.attrs` | `Object` | A map of URL parameter values
**returns** | `Array<Vnode>|Vnode` | The [vnodes](vnodes.md) to be rendered

The `vnode` parameter is just `m(Component, m.route.param())` where `Component` is the resolved component for the route (after `routeResolver.onmatch`) and `m.route.param()` is as documented [here](#mrouteparam). If you omit this method, the default return value is `[vnode]`, wrapped in a fragment so you can use [key parameters](#key-parameter). Combined with a `:key` parameter, it becomes a [single-element keyed fragment](keys.md#single-child-keyed-fragments), since it ends up rendering to something like `[m(Component, {key: m.route.param("key"), ...})]`.
The `vnode` parameter is just `m(Component, m.route.param())` where `Component` is the resolved component for the route (after `routeResolver.onmatch`) and `m.route.param()` is as documented [here](#mrouteparam). If you omit this method, the default return value is `[vnode]`, wrapped in a fragment so you can use [key parameters](#key-parameter). Combined with a `:key` parameter, it becomes a [single-element keyed fragment](keys.md#reinitializing-views-with-single-child-keyed-fragments), since it ends up rendering to something like `[m(Component, {key: m.route.param("key"), ...})]`.

---

Expand Down Expand Up @@ -403,7 +403,7 @@ It's possible to have multiple arguments in a route, for example `/edit/:project

When a user navigates from a parameterized route to the same route with a different parameter (e.g. going from `/page/1` to `/page/2` given a route `/page/:id`, the component would not be recreated from scratch since both routes resolve to the same component, and thus result in a virtual dom in-place diff. This has the side-effect of triggering the `onupdate` hook, rather than `oninit`/`oncreate`. However, it's relatively common for a developer to want to synchronize the recreation of the component to the route change event.

To achieve that, it's possible to combine route parameterization with the virtual dom [key reconciliation](keys.md) feature:
To achieve that, it's possible to combine route parameterization with [keys](#reinitializing-views-with-single-child-keyed-fragments) for a very convenient pattern:

```javascript
m.route(document.body, "/edit/1", {
Expand All @@ -421,7 +421,7 @@ Or even use the [`history state`](#history-state) feature to achieve reloadable

`m.route.set(m.route.get(), null, {state: {key: Date.now()}})`

Note that the key parameter works only for component routes. If you're using a route resolver, you'll need to use a [single-child keyed fragment](keys.md), passing `key: m.route.param("key")`, to accomplish the same.
Note that the key parameter works only for component routes. If you're using a route resolver, you'll need to use a [single-child keyed fragment](keys.md#reinitializing-views-with-single-child-keyed-fragments), passing `key: m.route.param("key")`, to accomplish the same.

#### Variadic routes

Expand Down
27 changes: 27 additions & 0 deletions docs/vnodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,30 @@ When creating libraries that emit vnodes, you should use this module instead of
Vnodes are supposed to represent the state of the DOM at a certain point in time. Mithril's rendering engine assumes a reused vnode is unchanged, so modifying a vnode that was used in a previous render will result in undefined behavior.

It is possible to reuse vnodes to prevent a diff, but it's preferable to use the `onbeforeupdate` hook to make your intent clear to other developers (or your future self).

#### Avoid passing model data directly to components via attributes

The `key` property may appear in your data model in a way that conflicts with Mithril's key logic, and your model might itself be a mutable instance with a method that shares a name with a lifecycle hook like `onupdate` or `onremove`. For example, a model might use a `key` property to represent a customizable color key. When this changes, it can lead to components receiving wrong data, changing positions unexpectedly, or other unexpected, unwanted behavior. Instead, pass it as an attribute so Mithril doesn't misinterpret it (and so you still can potentially mutate it or call prototype methods on it later on):

```javascript
// Data model
var users = [
{id: 1, name: "John", key: 'red'},
{id: 2, name: "Mary", key: 'blue'},
]

// Later on...
users[0].key = 'yellow'

// AVOID
users.map(function(user){
// The component for John will be destroyed and recreated
return m(UserComponent, user)
})

// PREFER
users.map(function(user){
// Key is specifically extracted: data model is given its own property
return m(UserComponent, {key: user.id, model: user})
})
```