Javascript / React window.onerror fired twice - javascript

I want to globally catch errors in my React application.
But every time the error is caught/forwarded twice to my registered function.
Example code:
window.onerror = (msg, url, lineNo, columnNo, error) => {
console.log(msg)
alert(msg)
}
class TodoApp extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<button onClick={(e)=>{
console.log("clicked")
null.bla
}}>
Create an error
</button>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
Here is a JS-fiddle:
https://jsfiddle.net/dmxur0rc/4/
The console only shows one 'clicked' log, so it's not the button that fires twice, but the error event.

It is known react error, related with implementation of error boundaries.

I found a basic solution to this that should work in all scenarios.
It turns out that the object is identical in all calls, you could set up something to match them exactly, or you could just attach a custom attribute to the error object...
Admittedly this may only work with window.addEventListener('error', function...), as you are given the genuine error object as an argument, as opposed to window.onerror = function... which gets the data parts, such as message & lineNumber as opposed to the real error.
This is basically how I'm using it:
window.addEventListener('error', function (event) {
if (event.error.hasBeenCaught !== undefined){
return false
}
event.error.hasBeenCaught = true
// ... your useful code here
})
If this is called with the same error twice it will exit before getting to your useful code, only executing the useful code once per error.

You need to return true from your error handler otherwise the default error handler will fire:
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror
When the function returns true, this prevents the firing of the default event handler.
Also note that other error handlers may be in place via addEventHandler.

As mentioned in other answers, the problem is in React in DEV mode. In this mode it re-throws all exceptions to "improve debugging experience".
I see 4 different error scenarios
Normal JS errors (for example, from an event handler, like in the question).
These are sent to window.onerror twice by React's invokeGuardedCallbackDev.
JS errors that happen during render, and there is no React's error boundary in the components tree.
The same as scenario 1.
JS errors that happen during render, and there is an error boundary somewhere in the components tree.
These are sent to window.onerror once by invokeGuardedCallbackDev, but are also caught by the error boundary's componentDidCatch.
JS errors inside promises, that were not handled.
These aren't sent to window.onerror, but rather to window.onunhandledrejection. And that happens only once, so no problem with this.
My workaround
window.addEventListener('error', function (event) {
const { error } = event;
// Skip the first error, it is always irrelevant in the DEV mode.
if (error.stack?.indexOf('invokeGuardedCallbackDev') >= 0 && !error.alreadySeen) {
error.alreadySeen = true;
event.preventDefault();
return;
}
// Normal error handling.
}, { capture: true });

I use this error boundary to handle both React and global errors. Here is some advice from the React documentation:
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed.
A class component becomes an error boundary if it defines either (or
both) of the lifecycle methods static getDerivedStateFromError() or
componentDidCatch().
Only use error boundaries for recovering from unexpected exceptions;
don’t try to use them for control flow.
Note that error boundaries only catch errors in the components below them in the tree; An error boundary can’t catch an error within itself.
class ErrorBoundary extends React.Component {
state = {
error: null,
};
lastError = null;
// This lifecycle is invoked after an error has been thrown by a descendant component. It receives the error that was thrown as a parameter and should return a value to update state.
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
error,
};
}
componentDidMount() {
window.onerror = (msg, url, line, column, error) => {
this.logError({
error,
});
};
}
// getDerivedStateFromError() is called during the “render” phase, so side-effects are not permitted. For those use cases, use componentDidCatch() instead.
// This lifecycle is invoked after an error has been thrown by a descendant component. It receives two parameters:
// error - The error that was thrown.
// info - An object with a componentStack key containing
componentDidCatch(error, info) {
// avoid calling log error twice
if (this.lastError && this.lastError.message === this.state.error.message) {
return true;
}
// Example "componentStack":
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
// logComponentStackToMyService(info.componentStack);
this.logError({
error,
info,
});
}
async logError({
error,
info
}) {
this.lastError = error;
try {
await fetch('/error', {
method: 'post',
body: JSON.stringify(error),
});
} catch (e) {}
}
render() {
if (this.state.error) {
return display error ;
}
return this.props.children;
}
}

Another way is to store the last error's message in state and check when it happens for the second time.
export default MyComponent extends React.Component{
constructor(props){
super(props);
this.state = {message: null};
}
componentDidMount(){
const root = this;
window.onerror = function(msg, url, line, column, error){
if(root.state.message !== msg){
root.setState({message: msg});
// do rest of the logic
}
}
}
}
But anyways it is good idea to use React Error Boundaries. And you can
implement this global javascript error handling inside the error
boundary component. Where you can both catch js errors (with
window.onerror) and React errors (with componendDidCatch).

My workaround: Apply debouncing (in typescript):
import { Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
let errorObserver: any;
window.onerror = (msg, url, lineNo, columnNo, error) => {
if (!errorObserver) {
new Observable(observer => {
errorObserver = observer
}).pipe(debounceTime(300)) // wait 300ms after the last event before emitting last event
.pipe(distinctUntilChanged()) // only emit if value is different from previous value
.subscribe(handleOnError)
}
errorObserver.next(
{
msg,
url,
lineNo,
columnNo,
error
}
)
return true
}
const handleOnError = (value: any) => {
console.log('handleOnError', value)
}

This looks like it's probably firing twice due to the nature of running it on JSFiddle. In a normal build process (with webpack and babel) code with a script error like that should fail to transpile.

Related

Reference to javascript object apparently returning different values in different places with no modifications in between

I'm using a variable twice within a function but it returns different values even though I'm making no modifications to it.
This is happening within a form component developed with Vue.js (v2) which dispatches a Vuex action. I think this has nothing to do with Vue/Vuex per se, but it's important to understand part of the code.
Here is the relevant piece of code from my component
import { mapActions } from 'vuex'
export default {
data() {
return {
product: {
code: '',
description: '',
type: '',
productImage: [],
productDocs: {},
}
}
},
methods: {
...mapActions(['event']),
save() {
console.log("this.product:", this.product)
const valid = this.$refs.form.validate() // this validates the form
console.log("this.product:", this.product)
if (valid) {
try {
this.event({
action: 'product/addProduct',
data: this.product
})
}
finally {
this.close()
}
}
},
// other stuff
and a small piece of code for the vuex action "event"
event: async ({ dispatch }, event) => {
const time = new Date()
const evid = `${Date.now()}|${Math.floor(Math.random()*1000)}`
console.log(`Preparing to dispatch... Action: ${event.action} | data: ${JSON.stringify(event.data)} | Event ID: ${evid}`)
// enriching event
event.evid = evid;
event.timestamp = time;
event.synced = 0
// Push user event to buffer
try {
await buffer.events.add(event)
} catch (e) {
console.log(`Error writing event into buffer. Action ${event.action} | evid: ${evid} `)
}
// dispatch action
try {
await dispatch(event.action, event)
}
catch (err) {
console.log(`Error dispatching action: ${event.action} | data: ${event.data}\n${err.stack || err}`)
window.alert('Could not save. Try again. \n' + err + `\n Action: ${event.action} | data: ${event.data}`)
}
},
The problem is with this.product. I've placed the several console.log to check out the actual values because it wasn't working as expected. The logs from the save() functions return undefined, but within the event function (a vuex action) the values are as expected, as shown in the console logs:
When I log this.product in the save() function. Both logs are the same.
When I log the event in the vuex action, it shows that event.data is actually the product.
I must be doing something terribly wrong here, but I'm totally blind to it. Any help is appreciated.
#Sumurai8: thanks for editing the question and for the hint.
Part of this may be because of that tiny i next to the opened product.
If you hover over it, it says that "the object has been evaluated just
now", which means it evaluates what is in the object when you open the
object, which is way after executing the action. [...] Whatever is
changing the product may very well happen after the event somewhere.
It actually helped me find the solution.
Basically within the this.close function called in the finally statement of the save() function, I was resetting the form and thus this.product, which was used solely to hold the form data. So at evaluation time, the object had undefined properties, while the event function managed to output to the console before the reset. However at the end the store would not get updated as expected (that's how I noticed the issue), because the event function and the action called within it are asynchronous and so the value got reset before the actual mutation of the vuex store.
Logging JSON.stringify(this.product) outputted the right value even within the save() method. I used that to create a more robust copy of the data and passed that to the event function as follows:
this.event({
action: 'product/addProduct',
data: JSON.parse(JSON.stringify(this.product))
})
Now everything works like a charme.

Subscribing to a change, but needing to see variable from where Service is used?

My code has been refactored and some extracted into a service that subscribes to functions. However, my original code had a call within the subscription that referenced a variable within the file, but now I'm not sure how to best reach it?
I am struggling with where to place the line:
this.select.reset('some string'); found within the subscribeToMessageService() function.
Original code
event.component.ts
select: FormControl;
#ViewChild('mySelect') mySelect: ElementRef;
subscribeToMessageService() {
this.messageService.serviceMsg
.subscribe(res => {
// unrelated code
this.select.reset('some string');
});
}
subscribeToEventService() {
this.eventService.eventSubject
.subscribe(res => {
this.select = new FormControl(res.status);
this.select.valueChanges.subscribe(value => {
// manual blurring required to stop error being thrown when popup appears
this.selector.nativeElement.blur();
// do something else
});
});
}
Refactored code
status.service.ts
subscribeToMessageService(): void {
this.messageService.serviceMsg
.subscribe(res => {
// unrelated code
// This is where 'this.select.reset('some string');' would have gone
});
}
status.component.ts
select: FormControl;
#ViewChild('exceptionalSelect') selector: ElementRef;
subscribeToEventService() {
this.eventService.eventSubject
.subscribe(res => {
this.select = new FormControl(res.status);
this.select.valueChanges.subscribe(value => {
// manual blurring required to stop error being thrown when popup appears
this.selector.nativeElement.blur();
this.onStatusChange(value);
});
});
}
Since you still want to subscribe to the original source messageService.serviceMsg your new StatusService needs to expose this observable to the injecting component (StatusComponent).
This can be done for example by creating a public observable in the StatusService (possibly by utilising rxjs Subject or angular EventEmitter) and triggering the emit in the subscription of messageService.serviceMsg.
Then your StatusComponent only needs to inject StatusService and do
this.statusService.serviceMsg // <-- might choose another name to make clear that this is passed on.
.subscribe(res => {
// unrelated code
this.select.reset('some string');
});

Invariant failed: Changing observed observable values outside actions is not allowed

How can I write this with mobx and axios? I am trying to use the arrow function to keep the scope of "this" but I might be doing it wrong.
#action saveMode() {
axios.post('/Course/Post', { Name: "test41515"})
.then(response => {
console.log(response, this.isEditing);
this.isEditing = !this.isEditing;
this.failedValidation = [];
})
}
Uncaught (in promise) Error: [mobx] Invariant failed: Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an `action` if this change is intended. Tried to modify: Course#5.isEditing
at invariant (app.bundle.js:7316)
at fail (app.bundle.js:7311)
at checkIfStateModificationsAreAllowed (app.bundle.js:7880)
at ObservableValue.prepareNewValue (app.bundle.js:5786)
at setPropertyValue (app.bundle.js:6663)
at Course.set [as isEditing] (app.bundle.js:6631)
at app.bundle.js:62336
at <anonymous>
MobX has a whole page in their documentation about writing async actions. That would be the place to start, https://mobx.js.org/best/actions.html, and in fact the first example is exactly the issue you're facing with the note
The above example would throw exceptions, as the callbacks passed to the fethGithubProjectsSomehow promise are not part of the fetchProjects action, as actions only apply to the current stack.
Given your snippet, the easiest fix is to use runInAction
#action saveMode() {
axios.post('/Course/Post', { Name: "test41515"})
.then(response => {
runInAction(() => {
console.log(response, this.isEditing);
this.isEditing = !this.isEditing;
this.failedValidation = [];
});
})
}

Adding a loading animation when loading in ReactJS

I would like to add a loading animation to my website since it's loading quite a bit when entering the website. It is built in ReactJS & NodeJS, so I need to know specifically with ReactJS how to add a loading animation when initially entering the site and also when there is any loading time when rendering a new component.
So is there a way to let people on my website already, although it's not fully loaded, so I can add a loading page with some CSS3 animation as a loading screen.
The question is not really how to make a loading animation. It's more about how to integrate it into ReactJS.
Thank you very much.
Since ReactJS virtual DOM is pretty fast, I assume the biggest load time is due to asynchronous calls. You might be running async code in one of the React lifecycle event (e.g. componentWillMount).
Your application looks empty in the time that it takes for the HTTP call. To create a loader you need to keep the state of your async code.
Example without using Redux
We will have three different states in our app:
REQUEST: while the data is requested but has not loaded yet.
SUCCESS: The data returned successfully. No error occurred.
FAILURE: The async code failed with an error.
While we are in the request state we need to render the spinner. Once the data is back from the server, we change the state of the app to SUCCESS which trigger the component re-render, in which we render the listings.
import React from 'react'
import axios from 'axios'
const REQUEST = 'REQUEST'
const SUCCESS = 'SUCCESS'
const FAILURE = 'FAILURE'
export default class Listings extends React.Component {
constructor(props) {
super(props)
this.state = {status: REQUEST, listings: []}
}
componentDidMount() {
axios.get('/api/listing/12345')
.then(function (response) {
this.setState({listing: response.payload, status: SUCCESS})
})
.catch(function (error) {
this.setState({listing: [], status: FAILURE})
})
}
renderSpinner() {
return ('Loading...')
}
renderListing(listing, idx) {
return (
<div key={idx}>
{listing.name}
</div>
)
}
renderListings() {
return this.state.listing.map(this.renderListing)
}
render() {
return this.state.status == REQUEST ? this.renderSpinner() : this.renderListings()
}
}
Example using Redux
You can pretty much do the similar thing using Redux and Thunk middleware.
Thunk middleware allows us to send actions that are functions. Therefore, it allows us to run an async code. Here we are doing the same thing that we did in the previous example: we keep track of the state of asynchronous code.
export default function promiseMiddleware() {
return (next) => (action) => {
const {promise, type, ...rest} = action
if (!promise) return next(action)
const REQUEST = type + '_REQUEST'
const SUCCESS = type + '_SUCCESS'
const FAILURE = type + '_FAILURE'
next({...rest, type: REQUEST})
return promise
.then(result => {
next({...rest, result, type: SUCCESS})
return true
})
.catch(error => {
if (DEBUG) {
console.error(error)
console.log(error.stack)
}
next({...rest, error, type: FAILURE})
return false
})
}
}

Running a function in a VueJs 2 component once its loaded

How do I fetch data once a component has been mounted? I start my vue instance and then load in the component, the component template loads in fine but the function calls in mounted are never run so the stats object remains empty, in turn, causing errors in the component/template that requires the data.
So how do I run a certain function on component load?
For what its worth... the functions I want to call will all make REST requests but each component will be running different requests.
Vue.component('homepage', require('./components/Homepage.vue'), {
props: ["stats"],
mounted: function() {
this.fetchEvents();
console.log('afterwards');
},
data: {
loading: true,
stats: {}
},
methods: {
fetchEvents: function() {
this.$http.get('home/data').then(function(response) {
this.stats = response.body;
this.loading = false;
}, function(error) {
console.log(error);
});
}
}
});
const vue = new Vue({
el: '#main',
mounted: function() {
console.log('main mounted');
}
});
You are already doing it fine by putting all the initialization stuff into mounted. The reason your component is not refreshing is probably because of binding of this, as explained below:
In your fetchEvents function, your $http success handler provides a response, which you are attempting to assign to this.stats. But it fails because this points to that anonymous function scope and not to Vue component.
To overcome this issue, you may use arrow functions as shown below:
fetchEvents: function() {
this.$http.get('home/data').then(response => {
this.stats = response.body;
this.loading = false;
}, error => {
console.log(error);
});
}
Arrow functions do not create its own scope or this inside. If you use this inside the arrow function as shown above, it still points to Vue component, and therefore your component will have its data updated.
Note: Even the error handler needs to use arrow function, so that you may use this (of Vue component) for any error logging.

Categories