diff --git a/docs/components.md b/docs/components.md
index 3f31a8cb..db68927b 100644
--- a/docs/components.md
+++ b/docs/components.md
@@ -77,11 +77,31 @@ To learn more about lifecycle methods, [see the lifecycle methods page](lifecycl
Like all virtual DOM nodes, component vnodes can have state. Component state is useful for supporting object-oriented architectures, for encapsulation and for separation of concerns.
-The state of a component can be accessed two ways: via `vnode.state` and via the `this` keyword in component methods.
+The state of a component can be accessed three ways: as a blueprint at initialization, via `vnode.state` and via the `this` keyword in component methods.
+
+#### At initialization
+
+Any property attached to the component object is deep-cloned for every instance of the component. This allows simple state initialization.
+
+In the example below, `data` is a property of the `Input` component's state object.
+
+```javascript
+var ComponentWithInitialState = {
+ data: "Initial content",
+ view: function(vnode) {
+ return m("div", vnode.state.data)
+ }
+}
+
+m(ComponentWithInitialState)
+
+// Equivalent HTML
+//
Initial content
+```
#### Via vnode.state
-State can be accessed via the `vnode.state` property, which is available to all lifecycle methods as well as the `view` method of a component.
+State can also be accessed via the `vnode.state` property, which is available to all lifecycle methods as well as the `view` method of a component.
```javascript
var ComponentWithDynamicState = {
diff --git a/examples/editor-bundle/index.html b/examples/editor-bundle/index.html
index ef3cc014..2fee9688 100644
--- a/examples/editor-bundle/index.html
+++ b/examples/editor-bundle/index.html
@@ -19,9 +19,7 @@ h1,h2,h3,h4,h5,h6,p {margin:0 0 10px;}
var m = require("../../mithril")
var Editor = {
- oninit: function() {
- this.data = "# Markdown Editor\n\nType on the left panel and see the result on the right panel"
- },
+ data: "# Markdown Editor\n\nType on the left panel and see the result on the right panel",
view: function(vnode) {
return [
m("textarea.editor-input", {oninput: function(e) {vnode.state.data = e.target.value}}, vnode.state.data),
diff --git a/examples/editor/index.html b/examples/editor/index.html
index 3ed14b14..9ea3c931 100644
--- a/examples/editor/index.html
+++ b/examples/editor/index.html
@@ -25,9 +25,7 @@ renderer.setEventCallback(run)
var trust = require("../../render/trust")
var Editor = {
- oninit: function() {
- this.data = "# Markdown Editor\n\nType on the left panel and see the result on the right panel"
- },
+ data: "# Markdown Editor\n\nType on the left panel and see the result on the right panel",
view: function(vnode) {
return [
m("textarea.editor-input", {oninput: function(e) {vnode.state.data = e.target.value}}, vnode.state.data),
diff --git a/render/tests/test-component.js b/render/tests/test-component.js
index 862d36d2..1b51d8e0 100644
--- a/render/tests/test-component.js
+++ b/render/tests/test-component.js
@@ -576,4 +576,22 @@ o.spec("component", function() {
o(vnode.dom).notEquals(updated.dom)
})
})
+ o.spec("state", function() {
+ o("deep copies state", function() {
+ var called = 0
+ var component = {
+ data: [{a: 1}],
+ oninit: init,
+ view: function() {
+ return ""
+ }
+ }
+
+ render(root, [{tag: component}])
+
+ function init(vnode) {
+ o(vnode.state.data).deepEquals([{a: 1}])
+ }
+ })
+ })
})