Apexcharts Updating React state inside an event - javascript

so I am trying to update a boolean state using this.setState inside the event dataPointMouseEnter of a Apexcharts line graph. However, I got an error saying
Uncaught TypeError: Cannot read property 'filter' of null
at e.filter (apexcharts.common.js:15731)
at t.value (apexcharts.common.js:622)
at t.value (apexcharts.common.js:662)
at t.value (apexcharts.common.js:1095)
Once I have this error, the event dataPointMouseLeave will not trigger.
I tried to see where the exact problem is so I console logged out some statement, and it seems that the code gets this error while I have the line this.setState({ showYAxis: true })
Below is the code I have for this part
The options configuration on the events part for the graph, and the returning component
state = {showYAxis: false, series: []}
render() {
const options = {
chart: {
events: {
dataPointMouseEnter: this.onDataPointMouseEnter,
dataPointMouseLeave: this.onDataPointMouseLeave,
}
}
}
return (
<Chart
type="line"
height="500"
options={options}
series={this.state.series}
/>
);
}
These are the code for the two helper functions onDataPointMouseEnter and onDataPointMouseLeave
onDataPointMouseEnter = (event, chartContext, config) => {
const { series, showYAxis } = this.state;
if(series && series[config.seriesIndex].name === "myYValue") {
this.setState({ showYAxis: true });
}
}
onDataPointMouseLeave = (event, chartContext, config) => {
console.log("This line should be printed in console when mouse leave data point"); // This line did not get called
}
When I remove the line this.setState({ showYAxis: true }), both functions gets called when mouse enter and mouse leave the data point. I'm trying to hide/show the y axis and the way I'm approaching this is to use setState to call a re-render, but now setState is causing an error. I would appreciate any kind of help and advise.

Related

Using WaveSurfer.js v2 in React Component

I'm trying to use WaveSurfer.js(version 2) with React.
I know that for version 1 there was a library called react-wavesurfer, but I really want to do it with v2.
I'm already experienced with it, without React. So, I just made a component of mine.
As you can see in the code below, it all works perfectly, the WaveSurfer object is generated correctly in the componentDidMount() but then, all of sudden, in the load() method, there's an error I can't understand correctly.
This error is throwed after the load(), and after the error, it's logged in console "There it happens!" and the wave property of the state, as it should be.
This is the error in question and by itself it doesn't mean nothing. That's just an error without stack trace in a obfuscated function in react-error-overlay. The second error, DOMException, is directly caused by the first, and they everytime are throwed together.
This is the row of the react-error-overlay that causes the error directly, in /node_modules/react-error-overlay/index.js:1582, but it's obfuscated.
// ... various imports
class Track extends Component {
wavref = null;
constructor(props) {
super(props);
let id = this.props.id;
this.state = {
id: id,
wave: null
};
}
load() {
console.log(this.state.wave, this.props.audio); // this.props.audio is the correct path, and should work correctly: "../../demo.wav".
this.state.wave.load(this.props.audio);
console.log("There it happens!", this.state.wave);
}
componentDidMount() {
let generatedWave = WaveSurfer.create({
container: ReactDOM.findDOMNode(this.waveref),
waveColor: this.state.color,
progressColor: this.state.progressColor,
plugins: [
RegionsPlugin.create({
dragSelection: {
slop: 5
}
}),
CursorPlugin.create({})
]
});
this.setState({
wave: generatedWave,
}, function() {
this.load();
});
}
render() {
return(
<div ref={(waveref) => this.waveref = waveref}></div>
);
}
}
export default Track;
Obviously, the load() method doesn't load/render the actual wave in the WaveSurfer canvas, and throws that error. I can't understand why, because it should just work normally and render the wave in the WaveSurfer canvas.
Does anyone of you know what can the error be?

ReactJS state reset itself

I am having a strange (to me) issue with my ReactJS code.
I just started learning ReactJS so I might be doing something very stupid, but this is what's happening.
Link to codepen: https://codepen.io/Scyleung/pen/XyVovg
I am have this component like so
class LeaseData extends React.Component {
constructor(props) {
super(props);
this.state = {
id: null,
ready: false,
data: null,
error: null,
};
this.update = this.update.bind(this);
//this.getNewJson(1);
}
update(newID) {
this.setState({
id: newID,
ready: false,
data: null,
error:null,
});
console.log("From Main: " + newID + " " + this.state.id);
this.getNewJson(newID);
}
getNewJson(id) {
let url = "https://hiring-task-api.herokuapp.com/v1/leases/"+id;
fetch(url)
.then(res => res.json())
.then(
(result) => {
console.log(result);
this.setState({
ready: true,
data: result
});
},
(error) => {
console.log("Error: " + error.message);
this.setState({
ready: true,
error: error
});
}
)
}
render() {
if (this.state.error) {
console.log("ERROR")
return (
<div>
<Form callback = {this.update}/>
Error: {this.state.error.message}
</div>);
} else if (!this.state.ready) {
console.log("Waiting")
return (<Form callback = {this.update}/>);
} else {
console.log("Displaying")
return (
<div>
<Form callback = {this.update}/>
{this.state.data.rent}</div>
);
}
}
}
I have a Form that calls update() when the user submit the form.
First issue, inside update() the console.log() always say this.state.id is null, even after submitting the form multiple times.
Second issue, update() should call getNewJson() that should set this.state.error, if an error happens in fetch. As much in render() it should console.log("ERROR"), which it does, so that's good. However immediately afterward it would go to console.log("Waiting") again. So maybe related to the first issue, since the state got reset.
Third issue, why am I getting an error on the fetch like 80% of the time?
The strangest thing is, if I uncomment the this.getNewJson(1) in the constructor, all the problems disappears!!
The main issue I assume is that onSubmit does what you want but then reloads the page cancelling the request. What you need to do to prevent this is to call e.preventDefault() after calling the callback function.
Changing your onSubmit to something like this should fix the main issue
onSubmit = {(e) => {
this.props.callback(this.state.value);
e.preventDefault();
}}
https://codepen.io/anon/pen/QJaeyY
Secondly, to get the error from fetch, you should use .catch() instead of the second callback function in then() as this is the proposed syntax and should work with most browsers.

Javascript / React window.onerror fired twice

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.

Chartjs + Vue.js - Cannot read property '_meta' of undefined

I am building an app using Vue.js + Chartjs. I am having a problem where I make a http call to a service to get data, parse it, and pass it into my Chartjs component. However, I keep getting the error Cannot read property '_meta' of undefined
Here are the relevant parts of my component:
<template>
<Chartjs :data="chartData" />
</template>
export default {
data () {
return {
chartData: false
}
},
created () {
this.getData()
},
methods: {
getData() {
const opts = {
url: 'some_url',
method: 'get'
}
request.callRoute(opts).then(results => {
this.chartData = results.data
}).catch(err => {
console.log(err)
})
}
},
components: {
Chartjs
}
}
Note - the chart renders fine if I hard code the chartData field with data that comes back from my request. However, it does NOT work if I make a http request first for my data.
Does anyone know what might be happening?
Thanks in advance!
Vue will render the component with the initial chartData (which is a boolean). You should use a v-if or other logic and render Chartjs component when you have the response. For example you can show a loading message/animation while the chartData is false.

Trying to debounce search function calls results in no event passed to a function

I am working with a library named lodash using its debounce() function to sort of limit api calls for search suggestions i.e. instead of calling api every time user changes a field I debounce it and wait 300ms from when they finish typing:
it looks like this in my component:
class myComponent extends Component {
//Handle listing search
searchListing(event) {
console.log(event.target.value);
}
//Render
render() {
const listingSearch = _.debounce( () => { this.searchListing() }, 300 );
return (
<TextInput onChange={listingSearch}/>
);
}
}
it works and calls the function however I get an error saying: Uncaught TypeError: Cannot read property 'target' of undefined
So I tried passing an event like so:
const listingSearch = _.debounce( (event) => { this.searchListing(event) }, 300 );
Now error says: Uncaught TypeError: Cannot read property 'value' of null after inspecting this event I'm passing, I can see that it has empty target, so is not working correctly.
Solution to my problem was to include event.persist(); inside my onChange handler function.
So:
searchListing(event) {
event.persist();
console.log(event.target.value);
}

Categories