How to have a loading icon between react router route transitions? - reactjs

Trying to get the preloader to show, fade out, and then show the new route. How to do this? Right now the page is flashing, then the preloader shows, and fades out to the new route.
I'm trying to use useLocation with useEffect(() => {...}, [location]) but it's causing the flash.
At the router level I have it add a loading class to the body so the preloader shows. Once the route component is mounted I'm removing that class. It seems like the useEffect that triggers on location change is getting called after the route render though.

Alright so this seems to have fixed it here is the code I had before.
useEffect(() => {
document.body.classList.remove('loaded');
window.scrollTo(0, 0);
const currentPath = location.pathname + location.search;
analytics.sendPageview(currentPath);
setTimeout(function () {
document.body.classList.add("loaded");
}, 1000);
}, [location]);
Here is the code I switched to that fixed it.
useEffect(() => {
return history.listen(location => {
document.body.classList.remove('loaded');
setTimeout(function () {
document.body.classList.add("loaded");
}, 1000);
})
}, [history])

Related

Undefined redux props when refresh page in react

I'm having trouble getting datas from redux when I refresh my page
const [filters, setFilters] = useState<string[]>(props.filters);
useEffect(() => {
(() => {
if (!_.isEqual(filters, props.filters)) {
setFilters(props.filters);
}
})();
});
My filters are undefined even though when I check redux devtools, there is datas in filters.
I need to trigger an event in my front to display my filters.
Anyone have an idea please ?
Edit:
(if I click any element in my page it load my filters)
If I add a setTimeout on refresh it works but I'm not sure using setTimeout is a solution
useEffect(() => {
setTimeout(() => {
setFilters(props.filters);
}, 1500);
}, []);
May be you just missed returning the inner functions:
Or just call the setFilters like:
useEffect(() => {
if (!_.isEqual(filters, props.filters)) {
setFilters(props.filters);
}
});

React component content disappears after page refresh

I am new to react and am having trouble figuring out why the data inside my Content component does not re-render on refresh.
When I visit one of the routes, say /sentences-of-the-day, and then I refresh the page, it seems all the stuff inside content is gone.
Can someone please help me out?
Here is the code sandbox:
https://codesandbox.io/s/mainichome-v7hrq
You need to load the data once the component is mounted (using useEffect) set to local state to trigger the render. In each refresh, mounting happens again and you have the data after each refresh.
Define another function in content.data.js
export const getContentData = () => {
return Promise.all(contentDataStories.map((func) => func()));
};
In your content.component.jsx
import { getContentData } from "./content.data.js";
const [content, setContent] = useState([]);
useEffect(() => {
(async () => {
setContent(await getContentData());
})();
}, []);
Code sandbox => https://codesandbox.io/s/mainichome-forked-4sx5n?file=/src/components/content/content.component.jsx:302-449
The problem is here:
import contentData from "./content.data.js";
//...
const [content] = useState(contentData);
That imports contentData and then sets it as state.
However, that value is asynchronous.
const contentData = [];
contentDataStories.forEach(function (func) {
func().then((json) => {
contentData.push(json);
});
});
export default contentData;
It's just [] until those promises reoslve.
So what's happening is that the page is loading fine, but the content from that file hasn't loaded before the first render.
This is a race condition. Which will happen first, the data loading or the render? Sometimes the render wins and everything is fine, but sometimes it doesn't and you get a blank page.
To fix it, you need to make React aware of your data loading, so that it can re-render when the data finishes loading.
First make a function that does your async loading:
export function getContentData() {
return new Promise((resolve) => {
// fetch async stuff here
resolve(myDataHere)
})
}
And then call that from a useEffect, which sets the state.
function Content() {
const { titleParam } = useParams();
const [content, setContent] = useState(contentData);
useEffect(() => {
getContentData().then(setContent);
}, [getContentData]);
//...
}
Now when you component mounts, it calls getContentData. And then that promise resolves, it sets the state, triggering a a new render.

nextjs router.events.on not called on intial page load

I have a working useEffect to call my google analytics when changing page.
This works fine when changing pages but when you initially load for the first time or refresh it does not call the router.events.on
this is my code
useEffect(() => {
if (cookies === true) {
router.events.on("routeChangeComplete", () => {
ReactGA.pageview(window.location.pathname);
});
return () => {
router.events.off("routeChangeComplete", () => {
ReactGA.pageview(window.location.pathname);
});
};
}
}, [router.events]);
I thought of using an other useEffect to initially call the reactGA but then when changing page it would be called twice, which is not good.
any idea on how to make this work on the initial page load?
That's expected behaviour - router.events is only triggered on client-side page navigations initiated by the Next.js router.
You can call ReactGA.pageview on a separate useEffect to handle initial page loads.
useEffect(() => {
if (cookies === true) {
ReactGA.pageview(window.location.pathname);
}
}, []);

React router page load issue

React router page load always keep the scrolling position same. Is there a simple way to keep the scroll position in a certain position of the page?
useEffect(() => {
let unlisten = history.listen(({ location, action }) => {
console.log(action, location.pathname, location.state);
window.scrollTo(0, 0)
});
return () => unlisten()
}, [])
You can listen to history change and scroll to top, or any other position you want, at history change. You can keep this code in your first, say App, component.

How to rerun useEffect when page is visible?

A react app using hooks. In useEffect there is an api-call to populate content on page.
Each element fetched is clickable. And a click will open details for the clicked element in a new browser window/tab.
When working in the new window, the user can make changes on that object. The user will then close that tab or at least just go back to the main window/tab.
Question is how I can detect that the user is coming back to the main window. This is because I want to re-fetch data from API. Thus, I want to rerun useEffect.
While googling I found this:
https://www.npmjs.com/package/react-page-visibility
Is that really what I'm looking for? Reading the docs I'm not really sure if that can be the solution to my issue. Is there another way to solve this?
You can use the visibilitychange event to achieve that:
const onVisibilityChange = () => {
if (document.visibilityState === 'visible') {
console.log("Tab reopened, refetch the data!");
}
};
useLayoutEffect(() => {
document.addEventListener("visibilitychange", onVisibilityChange);
return () => document.removeEventListener("visibilitychange", onVisibilityChange);
}, []);
Codesandbox
With React 18 you can use 'visibilitychange' with react brand new hook useSyncExternalStore
export function useVisibilityChange(serverFallback: boolean) {
const getServerSnapshot = () => serverFallback;
const [getSnapshot, subscribe] = useMemo(() => {
return [
() => document.visibilityState === 'visible',
(notify: () => void) => {
window.addEventListener('visibilitychange', notify);
return () => {
window.removeEventListener('visibilitychange', notify);
};
},
];
}, []);
Gist with hook
P.S:Don't forget cross-browser usage

Resources