I'm trying to call a function every time I refresh the page. I'm doing the following:
componentDidMount() {
this.props.fetchData(this.state.data);
window.addEventListener('beforeunload', this.handleRefresh);
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.handleRefresh);
}
And the function I'm trying to call is the following:
handleRefresh = () => {
// refreshPage here is an action method that talks to the backend
this.props.refreshPage(this.props.some_data)
}
The issue here is, the first couple of times I refresh the page, I get the expected output on the backend. After a couple of times, refreshing the page doesn't seem to trigger the backend method at all. Is this the correct way to trigger and get access to the refresh event?
Why are you using an event listener? React provides "lifecycle methods" that fire during specific stages of a page's life. If you want to call a function every time you refresh the page, just put the function inside componentDidMount or the class constructor. Those are executed whenever the page is loaded. See https://reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class
componentDidMount() {
this.props.refreshPage(this.props.some_data);
}
What is the purpose of fetchData? You seem to be running the function but not doing anything with its return value.
Related
I'm writing tests for a React component. It's a timer that starts counting down when you press a button, then stops when you press the same button. I have a test case that tries to press the pause button, wait a second, then press the pause button again, checking the timer to make sure that a second has elapsed:
Timer.test.js
render(<Timer />)
const pauseButton = screen.getByText('pause')
const timerOutput = screen.getAllByRole('heading')[1]
describe('Timer', () => {
test('Timer starts counting down when unpaused', done => {
function fetchTime(callback) {
fireEvent.click(pauseButton)
setTimeout(
fireEvent.click(pauseButton),
1250
)
return callback(timerOutput)
}
function callback(data) {
try {
expect(data).toHaveTextContent('24:59')
done()
} catch(error) {
done(error)
}
}
fetchTime(callback)
})
})
The problem is, the test doesn't seem to be hitting click on pauseButton the way I want it to. Jest tells me in the terminal when I run my test that timerOutput turns out to be '25:00' rather than '24:59', and it seems as if the component failed the test. But this is a problem with the test, not the component; when I run the app in my browser and press the button myself, it works the way it should. How do I get this test to work properly, and hit the buttons the way I want it to?
It's difficult to have an accurate answer with few information about the component itself.
First I would recommend use async arrow function inside test() whenever you need to handle async calls so you don't rely in callback hell.
Besides that, I would try to use jest.useFakeTimers() so you can advance the setTimeout timer in order to test properly. It seems that your second fireEvent.click never gets fired since the test checks it synchronously.
And I just noticed you requested the timerOutput at first but didn't request it after the click events.
I would suggest something like:
test('Timer starts counting down when unpaused', async () => {
jest.useFakeTimers();
fireEvent.click(pauseButton)
setTimeout(
() => {fireEvent.click(pauseButton)},
1250
)
jest.runPendingTimers(); // This would run the above function
expect(screen.getAllByRole('heading')[1]).toHaveTextContent('24:59')
}
})
Indeed, the expect statement would be better from a user perspective assertion, like:
expect(screen.getByText("24:59")).toBeVisible();
Since you don't matter about the HTML elements that contains that text content
Before this is flagged as a duplicate, I have checked the majority of other posts and solutions, to no avail. If anyone wants to double check, here they are:
1) Socket.io Multiple returns for a single event
2) Socket.io message event firing multiple times
3) socket.on event gets triggered multiple times
And many others.
Now to get to the meat of my question!
I have a socket in my client and my server code. Once my server-side socket emits a message, it is received by the client-side socket and prints out the message. With research, this probably ties into event listeners, but I can't find out how to apply it to my code. FYI the following client code is ran when a button is clicked.
Here are snippets of my client code
onButtonClick() {
socket.emit('message_to_server', 'ping');
socket.on('reply', (tmp) => {
console.log(tmp); // in this case, call it 'pong'
this.doSomethingWithMe(tmp);
});
}
doSomethingWithMe(msg) {
// do something with the information
}
The first time I click the button, I receive
> pong
The second time I click the button, I receive
> pong
> pong
It continues to grow exponentially.
I can post my server code if needed, but I'm 100% sure that it emits the information correctly.
Does anyone have any idea for fixing this issue? I can't figure out how the listeners play into this scenario, so I would appreciate any advice!
EDIT: I changed some of my code to the following:
import React ...
const socket = socketIOClient('http://localhost:3000')
socket.on('reply', (tmp) => {
console.log(tmp); // in this case, call it 'pong'
var inst = new drawMe();
inst.doSomethingWithMe(tmp);
});
class drawMe extends Component {
constructor(props) { this.state = { allData: ''}}
onButtonClick() {
...
}
doSomethingWithMe(data) {
this.setState({ allData: data });
}
I am now receiving an error saying that you cannot call setState on a component that is not yet mounted. I probably will not open another question for this issue, but I would appreciate any advice on it. If mods/anyone wants me to close, I have no problem doing so.
EDIT2: If anyone else has this issue, I made it work by moving the code to instantiate the socket and for the event inside my constructor.
You're attaching your event handler again every time the button is clicked. You have socket.on inside the click handler; that method attaches a new handler -- that is, each time it's run, it adds the specified function to the end of the list of functions to run when the event fires. So yes, every time you click the button, you're adding the function to the end of the list and it'll run once for every time it was added. (More accurately, since you're using an anonymous function there, it's creating a new function every time you click the button and adding it to the event handler list to be run when the event fires.)
You should only be attaching event handlers once, for instance just after creating the socket, not on every click.
Whenever I need to make an ajax request to the server without refreshing the page, Genexus should emit some event that I could use in my UC.
What exactly would be this event, and if there is none, how could I know every events in JS to use in an Genexus User Control?
I.e.:
If I click in an UserAction that may search for values in another table and then retrieve then for me, how can I capture this?
Another Scenario:
I'm making a request to the server and retrieving information of it. I need to get the
$(gx-warning-message).text();
but if my request is still loading, when I execute my function, it will return nothing, because the event is still loading up informations.
To avoid this, I'm making a looping, but it's not an elegant way to solve the issue.
$(".Button").click(function() {
var timesRun = 0;
var interval = setInterval(function(){
timesRun += 1;
if(timesRun === 60){
clearInterval(interval);
}
if ($(".gx-warning-message").text().length > 0) {
toastrgx();
$(".gx-warning-message").text('');
}
}, 200);
});
So, how can I make it better?
Thanks
If you want to be notified every time a GeneXus user event is fired, you can subscribe to gx.onafterevent event. For example:
gx.fx.obs.addObserver('gx.onafterevent', scope, function () {
// Here goes the code you want to execute
// every time a GeneXus event is fired
});
scope is the object you want to set as the this of the third parameter function.
I have a popover, that takes me to another page, where I pop back to the root page (popToRoot), reload the data/dom on an event and then dismiss the popup in the promise when the json data comes back from the server. It all works fine if I have a large timeout on the dismiss.
dismissPopup() {
if (this.popover) {
let that = this;
setTimeout(function () {
that.popover.dismiss();
}, 500);
}
}
If I make the timeout too low, say 100ms, it does not dismiss because the dom is still loading.
However, I don't think having a timeout is probably the best practice. What happens if someone has a slow devise, and the time is not enough?
Can anyone please make any suggestions? Should I detect when the dom has loaded, and then call dismiss? How do I check if the dom had loaded?
Thanks
Instead of using a timeout, you can use Events. By doing that, you can publish and event when the data comes back from the server (and everything is ready) and subscribe to that event to know when you need to dismiss the popup.
import { Events } from 'ionic-angular';
constructor(public events: Events) {}
// first page (publish an event when data is ready)
events.publish('loading:finished', data);
// second page (listen for the loading finished event)
events.subscribe('loading:finished', (eventData) => {
// eventData is an array of parameters, so grab our first and only arg
console.log('Data:', eventData[0]);
});
The popover can also be dismissed from within the popover's view by calling the dismiss() method on the ViewController
constructor(public navParams:NavParams,public navCtrl:NavController,public viewController:ViewController) {
console.log('Hello PopOverComponent Component');
}
blah()
{
//do something
this.viewController.dismiss();
}
I have a function stopRecording() that I'd like to be called when a timer runs out, or when someone presses a stop button. The problem is that when it is called when the timer runs out (the first half of the render function) it is called continuously, despite me bracketing it in an if clause. When it is called as a button event (in the return half of the render function) then it works fine.
Note my console logs. When I open the console in Chrome and let the timer run out, the console logs I marked as successful in my code body runs, but NOT ones that I commented with //!!!. I also get the following error continuously: Invariant Violation: setState(...): Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.
//...
stopRecording: function() {
if (this.state.recording){
console.log("this will log as expected")
this.setState({recordingStatus:"",
recording:false})
console.log("this will NOT log as expected'") //!!!
this.props.emit("recEmit")
}
}
render: function() {
var timeBar;
var countdown = "0";
var timeBarFill = "#FF9090"
if (this.state.recording){
countdown = new Date()-this.state.startTime
timeBarFill = "#FF3830";
if (countdown > this.state.maxRecLength){
console.log('this will log as expected')
countdown=0
this.stopRecording()
console.log('this will NOT log as expected') //!!!
};
}
//...
return(
//...
<button type="button" id="button" onClick={this.stopRecording}><b>Stop</b></button>
//...
)
You should never call setState inside render(): https://github.com/facebook/react/issues/5591#issuecomment-161678219
As render should be a pure function of the component's props and state, which means that it should not have any side effects (like changing its own state).
Also, you can't guarantee that React will call your component's render() method when your countdown is about to expire. Consider using setTimeout in component's life cycle methods.
I think that this is due to how states work in react. This article explains it pretty well. I suggest to read it but I can some it up for you:
setState is usually called asynchronously.
if setState is not triggered by an event that React can keep track of, such as onClick, it is called synchronously.
This means that when you are using onClick everything goes fine because your call of setState in stopRecording does not block and the function finishes before a re render is called. When a timer triggers it this happens synchronously, the state changes and render is called again.
Now, I still do not understand how it can run continuously, since it should have set the state.recording variable to false and I don't see anything that turns it back to true.
Also, be careful to use states just for variables that are truly states: change with time. The maxRecordinLength does not seem to be a state variable, and same for startTime.
EDIT:
after I saw the update I realized that the main issue here is changing a state inside of the render method. I posted this link in a comment here but I think it is worth explaining.
Basically, you can solve your issue by calling a setTimer function in the componentDidMount function of react-- more on this here.
Something like:
componentDidMount: function(){
setTimer(this.myFunction, this.props.maxRecLength);
},
And you myFunction would look like this:
myFunction: function(){
this.setState({timeElapsed: true});
},
Then you can use this.state.timeElapsed in your render function, and whatever is in there will be displayed after the maxRecLength is reached.