In the previous chapters, you rendered static HTML with hardcoded nodes. The next code snippet shows how to render a dynamic view with a list of posts.
Change you App.js to the following code:
import { app } from "hyperapp";
import html from "hyperlit";
const state = {
posts: [
{
username: "js_developers",
body: "Modern JS frameworks are too complicated",
},
{ username: "js_developers", body: "Modern JS frameworks are too heavy" },
{ username: "jorgebucaran", body: "There, I fixed it for you!" },
],
};
const listItem = (post) => html`
<li>
<strong>@${post.username}</strong>
<span> ${post.body}</span>
</li>
`;
const view = (state) => html`
<div>
<h4>hyperposts</h1>
<ul>
${state.posts.map(listItem)}
</ul>
</div>
`;
app({
init: state,
view,
node: document.getElementById("app"),
});
view
is extracted into a separate function.
Inside the view
you map over a list of posts
and render each of them using listItem
view fragment.
As a rule of thumb, if your view gets too big, split it into smaller view fragments.
Pass as much state as needed. For example: listItem
only needs a single post
parameter.
At the end of this section, your view should look like this:
Actions bring interactivity to your application. As users click buttons or type some text, you want to react to those events.
First, add a button just below the h4
element:
<h4>hyperposts</h4>
<button onclick=${AddPost}>Add Post</button>
The onclick
attribute translates to the DOM API click events.
More precisely, Hyperapp translates the onclick
into button.addEventListener('click')
.
Everything you know about the DOM API is still relevant and transferable.
There are no extra framework-specific events to learn.
Add the action itself. Put it between the state and view declarations:
const AddPost = (state) => {
const newPost = { username: "anonymous", body: "fixed text" };
return { ...state, posts: [...state.posts, newPost] };
};
AddPost
is a pure function mapping previous state to the new state.
When you click a button, Hyperapp automatically passes the previous state to your action.
newPost
is created and added to the end of the posts list.
A common pattern is to destructure the previous state and only update those properties that change.
Our current state has no other properties, but the code is future-proofed.
To keep your state updates simple, model your state as flat objects. The more nesting you do, the more
elaborate update strategies you will need (e.g. lenses).
In this book we'll call all actions with an uppercase letter.
The following figure shows the same action in a visual format:
Test your app in the browser and click the Add Post button several times. New items should be added to the list.
Hyperapp data flow is inspired by the Elm Architecture:
- view V interaction (e.g. click) triggers some action A
- action A creates a new state S
- new state S causes re-render of the view V
As a Hyperapp user, you declare all the views, actions and the initial state. Hyperapp connects the circles and takes care of:
- handling events
- dispatching actions
- re-rendering the view
This approach makes your code very declarative as you never have to perform fine-grained view updates. At any given time, your view is the HTML/DOM projection of your current state. And the state is the ultimate source of truth. In other words, the state is not spread across many JS components or even worse, in the DOM itself.
Note: with Hyperapp there's no need to use classes extending from a framework superclass or to decorate your code with framework-specific annotations. View and actions are pure functions, and state is a plain JS object. Therefore, cognitive overhead from unnecessary language features is minimal.