How to capture document.ready or window.load events in Gatsby - reactjs

I am relatively new to the Gatbsy framework and I am trying to figure out a way to toggle classes on some elements on DOMContentLoaded or on window.load, to animate them as soon as the user can see the screen.
This is what I did until now however it doesn't seem very appropriate:
componentDidMount = () => {
if (typeof window === "undefined") return
window.addEventListener("load", this.myEventHandler)
// or
if (typeof document === "undefined") return
document.addEventListener("DOMContentLoaded", this.myEventHandler)
}
Is there a better way of doing this?
Thank you in advance.

I think you should add these event listeners in the gatsby-browser.js:
// gatsby-browser.js
// ES6
export const onClientEntry = () => {
window.onload = () => { /* do stuff */ }
}
// or commonjs
exports.onClientEntry = () => {
window.onload = () => { /* do stuff */ }
}
You don't have to check for window there because this file is only run on the client side. Here's the full list of available hooks.
also AFAIK document doesn't have a ready event, it's a jQuery thing. You might be interested in DOMContentLoaded event, though there's some small different between that and jQuery's ready IIRC.

This is what I did until now however it doesn't seem very appropriate:
This is perfectly legitimate code imho:
componentDidMount = () => {
window.addEventListener("load", this.myEventHandler);
...
EDIT
Thinking about this, by the time componentDidMount has run, window "load" and document "ready" will already have fired... so it is a bit pointless.
https://codesandbox.io/s/n43z5x00j4
You can just use componentDidMount to check that the DOM has loaded and not bother with the other two events.

Related

Limit for function that can run before browser goes to background (mobile browser)

I have code to handle visibilitychange like below
As far as I know, browser has fraction of time to run function before the browser's visibility become hidden.
Furthermore, the OS(android or iOS) can freely decide to stop the any running functions and put the browser into background.
Here is my simple code:
const Component = () => {
const [isHidden, setIsHidden] = useState(false)
useEffect( function whenHidden() => {
if(isHidden){
// when page visible === false,
// the order of execution:
// setIsHidden(true) > useEffect() > whenHidden() > anotherLongLogic() > reactDOM rerender
// considering this function is 3rd in order above, does this function guaranteed to run?
anotherLongLogic()
}
},[isHidden])
useEffect(() => {
window.addEventListener('visibilitychange', (e) => {
if(document.hidden){
// the order when hidden is
setIsHidden(true)
// how long does this function allow to run?
anotherLongLogic()
}
})
})
}
My Questions are (for mobile browser mainly):
with react, is it guaranteed that setState and useEffect will run within the limited time?
Is there any limit for the anotherLongLogic (how much time is allowed to run this function)?
can we block or tell the OS to give more time to browser to complete any functions that is still running?
any best practice to handle this case?

Using refs to modify an element in React.js

Is it wrong to use refs to modify an element's properties? If so, why?
Example:
myRef.current.innerHTML = "Some content";
That's wrong if it's possible to modify the component's JSX to implement the change instead. Whenever possible, one should be able to determine the JSX that gets rendered solely from the current state of the component; direct DOM mutation side-effects like .innerHTML should only be done when there's no other possible option.
For this case, put the content into a state variable instead, like:
const [spanContents, setSpanContents] = useState('foobar');
const changeSpanContents = () => {
setSpanContents('Some content');
};
return (
<div>
<button onClick={changeSpanContents}>click</button>
<span>{spanContents}</span>
</div>
);
In some unusual cases, there exists no JSX syntax for the DOM mutation you want - for example, for putting a resize listener on the window. In such a case, you will have to resort to using vanilla DOM methods instead of doing it solely through React. The following pattern is common for such a case:
useEffect(() => {
const handler = () => {
// resize detected
};
window.addEventListener('resize', handler);
return () => {
window.removeEventListener('resize', handler);
};
}, []);
I wouldn't say wrong but it depends on what you are going to do after changing the html. As React will loose control over that element, you would have a hard time if you need to work on that DOM. Also, it is risky because React have controls over DOM, when you change it manually, it could lead to unexpected behaviors.

How to detect if the React app is actually being viewed

So I have created an app that shows data in realtime obtaining it from devices.
However, I want to make my server not obtain data when nobody is viewing the app.
So essentially I need some way to determine whether the app is currently being viewed, regardless of if it's desktop or mobile, this includes tab is on focus where the app is opened and that is what the user is currently viewing, and there is nothing on top of the browser, so browser opened on the correct tab, but user has explorer on top of it doing something entirely different, this for my case should be false, and for mobile, the same thing including if device is locked (screen off).
The reason for trying to do that, is to reduce the load on the devices, so that data is being requested, only when there is someone to view it.
From what I have researched I found out about the focus and blur events, but I was unable to make it work, and I don't even know if that is the correct approach, but what I have tried is:
Adding event listeners to the window in the App component:
function App() {
useEffect(() => {
window.addEventListener("focus", () => { console.log("viewed")});
window.addEventListener("blur", () => { console.log("hidden")});
})
}
Adding them as props to the App component within index.js:
<App onFocus={() => {console.log("viewed")}} onBlur={() => {console.log("hidden")}}/>
Neither had any kind of effect, I didn't get either of the console outputs.
Is that even the correct approach?
I would add a socket connection to the app. Then the server would be able to know if there are at least X persons connected and act accordingly.
I would suggest you to try socket for this kind of connection tested, but since you also wanna reduce the load for the user, testing if the user is focused in the browser is the way to go.
To achieve it, I won't add this code inside the React component because of the nature of React as all of its components are rendered inside the <div id="root"></div>, other parts of the html page will still be unaffected by this mechanism. So what you probably want to do is to add the code in the index.html and use window.userFocused to pass the value into React from the index
Edit: added focus/blur script
<script>
window.addEventListener("focus", function(event)
{
console.log("window is focused");
window.userFocused = true;
}, false);
window.addEventListener("blur", function(event)
{
console.log("window is blurred");
window.userFocused = false;
}, false);
</script>
So I ended up solving it with pretty much the same code as initiallywith a few slight modifications, I still used an useEffect hook:
const onFocusFunction = () => {
// do whatever when focus is gained
};
const onBlurFunction = () => {
// do whatever when focus is lost
};
useEffect(() => {
onFocusFunction();
window.addEventListener("focus", onFocusFunction);
window.addEventListener("blur", onBlurFunction);
return () => {
onBlurFunction();
window.removeEventListener("focus", onFocusFunction);
window.removeEventListener("blur", onBlurFunction);
};
}, []);
The best way is to use document.
document.onvisibilitychange = () => {
console.log(document.hidden)
}

External filtering grid with server side row model

React/Node app server side data for ag-grid
OnFilterChanged and IsExternalFilterPresent are both being triggered, but doesExternalFilterPass does not. I tried playing around with binding the functions and even forcing a trigger. Also tried getting the IsExternalFilterPresent to always return true, it still doesn't work. The main difference from the documentation example and my project is that I'm using server side data. If it is not supposed to work, in nowhere does it state in the doc that it's only client side (unlike Quick Filter which is clearly specified).
Edit: The lifecycle logic on my code. I don't know if adding how I'm fetching data from the server side is relevant. But here is the main logic I added for the external filter. This is inside the class component that renders the agGrid.
// On state change trigger filter change
componentDidUpdate (prevProps, prevState) = {
const filterChange = (prevState.filterDashboard !== this.state.filterDashboard);
if(filterChange){
console.log('filter triggered')
this.state.gridAPI.onFilterChanged()
}
}
// functions passed to my agGrid component
isExternalFilterPresent = () => {
console.log('filter present', this.state.filterDashboard);
// only trigger does filter pass if the filter is true
return this.state.filterDashboard
}
doesExternalFilterPass = (node) => {
console.log('filter pass', node)
if(this.state.filterDashboard){
for(let key in node.data){
if(node.data[key].toString().includes(this.state.filterString)) return true
}
} else return true
}

Several methods for setState instead of single one

I'm developing a React app without Redux or any other state manager.
Let's say I want to do three things when a button is clicked:
Enable some other button
Remove a label
Show a confirmation toaster
These 3 things are controlled by 3 variables of the state. I could therefore do simply this:
myHandler = () => {
this.setState({
canSave: true, // Enable the button
isLabelVisible: false, // Hide label
isConfirmationMessageVisible: true, // Show confirmation message
});
}
However, I could get rid of those comments by using some private class functions, like this:
myHandler = () => {
this.toggleSaveButton(true);
this.toggleLabel(false);
this.toggleConfirmationMessage(true);
}
toggleSaveButton= (enabled) => {
this.setState({
canSave: enabled,
});
}
toggleLabel= (visible) => {
this.setState({
isLabelVisible: visible,
});
}
toggleConfirmationMessage= (visible) => {
this.setState({
isConfirmationMessageVisible: visible,
});
}
In addition to remove those comments which could easily get out-of-sync with the code, this allows me to reuse the private methods in other places of my code.
Since this is handled in a synthetic event, I have read here that it will be batched, so I can expect no performance penalty.
My question is: is this good practice? have you used this approach? can you point some potential drawbacks I can not foresee right now?
This is perfectly fine. As you mention, React batches all updates to the state that are triggered from event handlers. This means that you can safely use multiple setState() like you are doing here.
In current release, they will be batched together if you are inside a React event handler. React batches all setStates done during a React event handler, and applies them just before exiting its own browser event handler.
The only thing you need to look out for is if you are changing the same state twice from two setState() calls. For example:
a() {
this.setState({foo: foo+1});
}
b() {
this.setState({foo: foo+1});
}
Calling a() and then b() from the same event, will increment foo by 1, not two.
Instead use:
a() {
this.setState(prevState => ({foo: foo+1}));
}
b() {
this.setState(prevState => ({foo: foo+1}));
}
this will correctly increment foo by 2.
For potential future readers who are not calling multiple setState() from an event handler, I should note the following:
With current version, several setStates outside of event handlers (e.g. in network responses) will not be batched. So you would get two re-renders in that case.
Alternative solution
What you can do though, regardless if you call setState() from an event handler or not, is to build an object and then set it as the new state. The potential benefit here is that you only set the state once and thus don't rely on batching or where the function was triggered from (event handler or not).
So something like:
myHandler = () => {
let obj = {}
obj = this.toggleSaveButton(obj, true);
obj = this.toggleLabel(obj, false);
obj = this.toggleConfirmationMessage(obj, true);
this.setState(obj);
}
toggleSaveButton= (obj, enabled) => {
obj.canSave = enabled;
return obj;
}
toggleLabel= (visible) => {
obj.isLabelVisible = visible;
return obj;
}
toggleConfirmationMessage= (visible) => {
obj.isConfirmationMessageVisible = visible;
return obj;
}

Resources