Migrate your Hyperapp v1 project to v2 with console warnings informing you what refactoring you will need to do.
npm i hyperapp-compat
import { h, app } from "hyperapp";
+import withCompat from "hyperapp-compat";
-app(state, actions, view, document.body);
+withCompat(app)({
+ init,
+ view,
+ subscriptions,
+ container: document.body
+});
Some examples are available to show this in action.
Although this package offers helpful messages on the steps needed to prepare your v1 Hyperapp for v2 sometimes it's helpful to identify what order one should attack the problem. The following steps are roughly in order of difficulty and prerequisites. By using this project you will be able to check your work in many of the intermediary states along the way.
Wrap your call to app
in a withCompat
call and convert the multiple arguments to a steamlined new props object:
// Before
app(state, actions, view, document.body);
// After
withCompat(app)({
init: () => state,
actions,
view,
container: document.body
});
The state can be initialized with the new init
function, but most everything else stays pretty much the same so far.
Hyperapp v1 supports a tree of nested actions but this concept has no equivalent in v2. Now is a good time to flatten this tree in order to make later steps easier on yourself. Remember that you are now operating on the entire state tree and not just a "slice" of it. Should be a fairly mechanical refactor, you got this!
// Before
const actions = {
counter: {
down: state => ({ count: state.count - 1 }),
up: state => ({ count: state.count + 1 })
}
};
const view = (state, actions) => (
<div>
<h1>{state.counter.count}</h1>
<button onclick={() => actions.counter.down()}>-</button>
<button onclick={() => actions.counter.up()}>+</button>
</div>
);
// After
const actions = {
down: state => ({ count: state.count - 1 }),
up: state => ({ count: state.count + 1 })
};
const view = (state, actions) => (
<div>
<h1>{state.count}</h1>
<button onclick={() => actions.down()}>-</button>
<button onclick={() => actions.up()}>+</button>
</div>
);
In v2 actions will no longer shallow merge partial state objects returned from actions. You will want to use the spread operator, Object.assign
, or similar.
// Before
const actions = {
up: state => ({ count: state.count + 1 })
};
// After
const actions = {
up: state => ({ ...state, count: state.count + 1 })
};
Actions used for side effects will need to be split up into actions and effects for v2.
You will be delighted to know that the hip new v2 style actions and effects can be imported from anywhere (including dynamically) and directly used in your view
- meaning you can now ditch this:
const state = {
count: 0
};
const actions = {
down: state => ({ count: state.count - 1 }),
up: state => ({ count: state.count + 1 })
};
const view = (state, actions) => (
<div>
<h1>{state.count}</h1>
<button onclick={() => actions.down()}>-</button>
<button onclick={() => actions.up()}>+</button>
</div>
);
withCompat(app)({
init: () => state,
actions,
view,
container: document.body
});
And instead write this:
const state = {
count: 0
};
const Down = state => ({ count: state.count - 1 });
const Up = state => ({ count: state.count + 1 });
const view = state => (
<div>
<h1>{state.count}</h1>
<button onclick={Down}>-</button>
<button onclick={Up}>+</button>
</div>
);
withCompat(app)({
init: () => state,
view,
container: document.body
});
Hyperapp v1 included a feature for components to access the global state and actions known as lazy components (sometimes called subviews). This feature was removed for v2 and so components will only receive the props that are passed into them. This may seem annoying if you are now having to pass props multiple levels deep, however this is more explicit and unmagical. This is also an opportunity to rethink the design of the components in your view and decide if there's a better way to organize things.
To accommodate calling actions in reponse to anything other than event listeners attached to nodes in your view
v1 returns actions for interop from app
. You should use init
for calling effects on app start and subscriptions for external events instead.
Lifecycle events have been removed for Hyperapp v2. For users of this feature replacing them may seem daunting, but luckily there is an issue describing many of the alternatives.
Hyperapp Compat is MIT licensed. See LICENSE.