Please forgive me if we've already had a discussion on this topic, and I know this is subject to change with the API proposals brewing, but I wanted to capture the state of affairs as of this moment.
Because of the indirection with events.action and events.resolve it's not possible to catch any errors that are thrown within the execution of an action today. I believe the same will also hold true for the coming hooks API.
It is possible to catch and handle view errors with an HOC.
React 16 is introducing Error Boundaries to handle this problem. I'm not proposing we add anything to core, but wondering what the best practice should be.
Here's a small demo pen showing view/sync action/async action errors and how one might handle them with an HOA: https://codepen.io/okwolf/pen/rGWpgJ?editors=0010
I like the idea of having something catch errors and allowing you to react to it, without crashing the app.
I don't like the idea of it being a component.
I feel there's an already existing problem (talking about React now) with the creation of humongous component trees filled with higher order components that distort the actual features of your application. I see a real danger of error boundaries becoming littered all over, adding to that.
But again, I like the idea! The problem to solve for me would then be, how can I create such a thing that also catches errors on itself, eliminating the need for a wrapper?
@johanalkstal @okwolf What about a new VDOM event?
app({
view(state) {
return (
<main onerror={() => console.log("Look what you've done!")} />
)
}
})
@JorgeBucaran I assume that event would only catch errors which occur during the creation of v-dom nodes? (inside the h calls)
@SkaterDad Yes, should it be otherwise?
Nope, just verifying. I think it would be a useful feature. There are definitely times when I'm iterating on frontend/backend, and get data mismatches.
@JorgeBucaran What about a new VDOM event?
Would the onerror function return a VNode to render in place of the node it's specified for? What would happen if you defined nested onerror props? 馃
@okwolf Hmm not sure. TBH I still don't understand why we need this in Hyperapp.
@JorgeBucaran without some way to respond to errors the user gets absolutely zero feedback that something even went wrong, it just looks like nothing happened. Unless they look in the log like us maniacs 馃槣
@okwolf What could possibly go wrong?
@JorgeBucaran What could possibly go wrong?
Far too many to list them all here, but using an example from @SkaterDad - if you get back junk data from an API and are trying to render it in the view you might try and reference properties of undefined and now all your user sees is the view where it's still loading the data from the server forever instead of an error.
So much can go wrong in JS land without any warning, causing the application to just stop working for the user without as much as a hint on why (unless you look in the console).
The idea with the so called Error Boundaries in React is to end that behavior, by catching errors and giving the developer the opportunity to display an alternate UI in such cases, instead of the application stopping dead in its tracks.
Does it have to be something that麓s a part of Hyperapp core? No.
Could it be? Yes if it麓s simple enough without going against the idea of Hyperapp.
I think the minimum would be some kind of built in event that catches errors propagated from itself or its children, that allows developer to add something to handle it and render an alternative view?
app({
view(state, actions) {
return h('div', { onerror: (err) => {
errorLog(err)
actions.renderFailed(true)
} }, [
state.renderFailed ? 'Ooops, here be dragons!' : 'Hello World'
]
})
})
That's a quick example but the idea would be that individual components could render an error view instead of breaking the entire application.
Guys,
What could possibly go wrong?
is a joke. A bad one. 馃檱
h('h1', { onerror: () => console.log("Can't you recognize sarcasm when you see it!?") }, 'What could possible go wrong?')
I think there are several types of errors to consider:
view which are synchronous during render. If this happens, it is unclear if the app can continue due to unknown render state.actions which can be synchronous or asynchronous.window.onerror.@pspeter3 Errors in view which are synchronous during render. If this happens, it is unclear if the app can continue due to unknown render state.
These are the easiest to deal with. You have your choice of an HOC, render prop/children function, or HOA that wraps the view function. I am curious which of these the community will prefer going forward.
@pspeter3 Errors in actions which can be synchronous or asynchronous.
In the case of synchronous errors, these can easily be caught in an HOA. Async is quite a bit tricker, however.
@pspeter3 Errors that are outside the tree entirely and caught by window.onerror.
Are these kinds of errors in scope for the current discussion regarding Hyperapp?
I like this discussion. Perhaps it's time now to start collecting API proposals? 馃
@okwolf Ping.
@JorgeBucaran give me some time to update the examples for 1.0.0, then we can discuss. I still don't think we should add anything to core just yet.
So I played around with a few different APIs for error handling in a 1.0.0+ world. I ended up with an HOA that adds an errors property to the state and an onError action. This action is called by the view and sync actions automatically, plus it's available to call from async actions.
Here is the pen if folks want to check out the approach for themselves: https://codepen.io/okwolf/pen/rGWpgJ?editors=0010
@okwolf What about a new onerror "lifecycle" event? Does that even make sense?
@JorgeBucaran What about a new onerror "lifecycle" event? Does that even make sense?
Are you referring to an onerror virtual node prop attribute? If so, my question from before still applies.
I'm also assuming this would only handle the issue of errors that occur in the view, but wouldn't catch errors in actions?
When actions are triggered from the view, e.g. button, link, onclick, etc..
The vnode from child to parent should be able to catch the error.
<button onclick={() => actions.doSomething()} onerror={(e) => actions.showErrorModal(e)}>
do something
</button>
But when the action is used from the outside or withing another action, we will need to use try/catch
try {
actions.doSomething()
} catch (e) {
actions.showErrorModal(e)
}
But why arent we promoting the use of try/catch directly in the action body ?
@Swizz Well, why aren't we? Do you use it yourself? 馃
I am not an example. I do not use try/catch mostly. But I didn't build a large scale app yet.
How do you catch errors at Qiita ?
We have try blocks in some few areas, and Promise's onRejected handlers in others. I want to use more async functions from now, and when I do, I plan to use try..catch.
I'm writing a decent sized app, but don't really see the value in adding special onerror events unless it's only for a dev-only build. If I write erroneous view code, that's on me!
In my opinion, catching errors is no different in hyperapp than any other context. try...catch if you're unsure if something will throw an error, .catch() your promises if you care about the errors.
On the subject of async functions, @JorgeBucaran, does Qiita transpile down to ES5 for deployment? I did a little experimenting with Babel vs Typescript's compilation of async/await, and found Typescript had a smaller polyfill than Babel (regenerator-runtime). Both compilers make the await blah part way bigger since it generates switch statements, but I like TS's implementation better visually.
It was just a toy example, though, so on a bigger codebase it might not be a big difference. You can tell the TS compiler to allow JS files, so as long as you aren't using any weird babel plugins you could compare the outputs.
@SkaterDad does Qiita transpile down to ES5 for deployment?
Yes. We use TypeScript, so we compile to whatever tsc compiles down to, which I believe is ES3.
@okwolf It almost feels wrong to close our oldest open issue without an actual solution, but I am going to close here in favor of #417. 馃憢
Most helpful comment
Guys,
is a joke. A bad one. 馃檱