Awesome
This project is only compatible with hyperapp v1, and I do not intend to maintain it any longer. Hence, I am archiving it.
hyperapp-nestable
A tiny wrapper around your app, that lets you embed it within other hyperapp apps, as if it were a component. Effectively answering the age-old question: "How do I make components with local state, like React's Object components, in Hyperapp?"
Install
...using npm
npm install hyperapp-nestable
And require
(or import
, if you're using es6 modules) in your project:
const nestable = require('hyperapp-nestable')
... or include in HTML from CDN
Add this to the <head>
of your page:
<script src="https://unpkg.com/hyperapp-nestable"></script>
this will export nestable
in the global scope.
Basic Usage
Use the nestable
function in the following way:
var ComponentName = nestable(initialState, actions, view)
... which is to say, just like you start a normal Hyperapp-app. The difference is, you don't provide a container. That is becuase instead of attaching an app to the dom immediately, nestable
returns a component-function which will start a hyperapp app, attached to the DOM, when and where the component is rendered.
An example:
/*
Define a stateful Counter component
*/
const Counter = nestable(
//STATE
{value: 0},
//ACTIONS
{
up: _ => state => ({value: state.value + 1}),
down: _ => state => ({value: state.value - 1}),
},
//VIEW
(state, actions) => (
<p>
<button onclick={actions.down}>-</button>
{state.value}
<button onclick={actions.up}>+</button>
</p>
)
)
/*
Use the Counter-component in an app
*/
app({}, {}, _ => (
<div class="app">
<h1>Look, a counter:</h1>
<Counter />
</div>
), document.body)
The above example will do exactly what you expect, and render a functioning counter after the heading.
Component's Tagname
If you look at the html of the app example above, you'll see it looks like:
<div class="app">
<h1>Look, a counter:</h1>
<x->
<p class="counter">
<button>-</button>
0
<button>+</button>
</p>
</x->
</div>
Notice the <x->
element in there. That element corresponds to the vnode in the main app's view, where the counter's view is rendered. It's necessary as a means to "connect" the two vtrees. But the tag name x-
is just the default. If you want it to be something more descriptive, we might call it counter-
(the dash on the end is just to make it clear it's not a real html element). You would do that by giving it as the fourth argument to the nestable
function:
const Counter = nestable(
initialCounterState,
counterActions,
counterView,
'counter-'
)
You could make the tag a regular html tag such as div
or section
too. You may want that for CSS-reasons.
Component properties & children
You can pass props and children to a component (just like any other). In order for your view to be aware of them, return a component from your view.
const MyComponent = nestable(
//STATE
{...},
//ACTIONS
{...},
//VIEW
(state, actions) => (props, children) => (
<div class={props.class}>
<h1>{state.foo}</h1>
{children}
<button onclick={actions.bar}>Go</button>
</
)
)
Special properties
key
They key property is set on the component's tag (not the top node of the component's view
). This can be very important when you have components as siblings to eachother in the virtual dom.
class
& id
These are set on the component's tag. Mainly useful for css purposes.
oncreate
, onupdate
, ondestroy
These lifecycle events are first use for managing the component, but afterward, will be called just as usual, for the component's node.
Nestable component lifecycle
The special action called init
If you add an action called init
to your component, this action will be called when the component is first rendered. It will be passed the component's props as its first argument.
Such as if you want your Counter
component to accept a starting value as a prop, you don't want the actual value to change when whatever was the basis for the starting value changes. So you could implement the counter this way:
const Counter = nestable(
//INITIAL STATE
{ value: 0 },
//ACTIONS
{
init: props => ({value: props.initial || 0}),
up: _ => state => ({value: state.value + 1}),
down: _ => state => ({value: state.value - 1}),
},
//VIEW
...
)
Now, the counter will start at the value it is given the first time it's rendered, but when that value changes, it will not affect the value of the counter.
Here's a live example
The special action called uninit
Corresponding to init
, if you need something done when the component is destroyed, you can put in an action named uninit
Here is a more complex, while somewhat contrived demonstrating many of the features mentioned here.