React scroll page to the top after transition - javascript

I want my application to go to the top of the page when another page within the site is clicked. I have used the code from: https://v5.reactrouter.com/web/guides/scroll-restoration and put in a separate file.
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
}
What I have tried:
I have placed <ScrollToTop /> before <Switch /> and after <BrowserRouter />as in the docs (these are in separate files).
Unlike the docs, the <App /> in my application is in a separate file, so <ScrollToTop /> is not placed near it.
When I console.log, I see it being hit at the correct instance (when there is a page change), but nothing occurs. I have also looked into many stackoverflow posts concerning the same issue, but I could not incorporate their suggestions.
What are possible issues:
Calling window.scrollTo(0, 0) outside the function does not seem to work. Are there any alternatives to this function?
Is the placement of <ScrollToTop /> incorrect? Does it have to be called near the <App />?

Welcome to Stackoverflow #user19251203
You have already gone half the way. You really don't need to create a component for it. You can create a custom hook and place it in your <App /> and then call it:
function App() {
// Scroll to top on every transition custom hook
const useScrollToTop = () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
};
useScrollToTop();
return (
<div className="App">
.......
</div>
)
}
This way whenever you go to a new page, it scrolls to the top of the page.

Related

Loading page on nextjs 13

Hi im trying to get a loading page to show while website is taking the time to load. as it quite a large website I thought a loading screen would provide the best possible user experience however I cannot seem to figure out how to get it to work on nextjs 13. I have created a simple functional component that says loading... and have imported it directly into my layout.jsx folder.
I am using the app directory method which is quite new and im also new at nextjs so im a little lost ^^
I imagine I might need to set state at some point but I cant seem to figure out when and where to do it
any advice would be great.
thanks
import "./globals.css";
import React, { useState, useEffect } from "react";
import Loading from "../components/loading/loading";
const Layout = ({ children, dataLoaded }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if (dataLoaded) {
setLoading(false);
}
}, [dataLoaded]);
return (
<body className="app {oswald.className}">
{loading && <Loading />}
{children}
</body>
);
};
export default Layout;
.
.
.
Attempt 1 -
After following one of the answers below it does not seem like my loading page is showing up at all. and no errors showing up.
my layout is as follows
layout.jsx
import "./globals.css";
import { Suspense } from "react";
import Loading from "../components/loading/loading";
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<Suspense fallback={<Loading />}>{children}</Suspense>
</body>
</html>
);
}
LoadingPage.js
const LoadingPage = () => {
return (
<div className="loading w-screen h-screen bg-red-100">
<p>Loading...</p>
</div>
);
};
export default LoadingPage;
Loading.js
import LoadingPage from "#/components/loading/loading";
export default function Loading() {
return <LoadingPage />;
}
In NextJS 13, there's actually a default way to handle loading states within pages. You can declare a loading.tsx file in your /app directory, with this content:
export default function Loading() {
return <Loading />
}
Then, inside your Layout, you can wrap your page with a Suspense tag, like this:
<Layout>
<Navbar>
...
<Suspense fallback={<Loading/>}>
<Page/>
</Suspense>
</Layout>
Your loading state will be automatically handled upon navigation.

react.js & django, useParams unable to navigate to the page

I am current building a react app with django, I am trying to navigate from the HomePage to the DataPage with corrsponding id. However, it return Page not found error. I am using react-router-dom v6.
Using the URLconf defined in robot.urls, Django tried these URL patterns, in this order:
admin/
api/
api-auth/
homepage
homepage/data
The current path, homepage/data/54, didn’t match any of these.
Here is my App.js
export default class App extends Component {
constructor(props) {
super(props);
}
renderHomePage() {
return (
<HomePage />
);
}
render() {
return (
<BrowserRouter>
<Routes>
<Route exact path='homepage/' element={this.renderHomePage()} />
<Route path='homepage/data/:id' element={<DataPage />} />
</Routes>
</BrowserRouter>
)
}
}
const appDiv = document.getElementById("app");
render(<App />, appDiv);
And I want to navigate to the DataPage below:
const EmtpyGrid = theme => ({
Grid: { ... }
});
function DataPage(props) {
const { classes } = props;
const { id } = useParams();
return (
<div>
... some material ui components ...
<div/>
)
};
DataPage.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(EmtpyGrid)(DataPage);
I was thinking whether I need configure my url.py in frontend as well, and I need to define a designated value for {id} returned from the materialui component first. Perhaps I need a button or <Link><Link/> for the navigation instead of just simply typing in the url? Still, no luck after many attempts. I am so confused right now.
If you know what is wrong with my code, please feel free to leave a comment. Thanks
After many tries and checking documents, I don't really need to configure my urls.py. I only things that I am missing is to put a parameter in my naviagate() from onRowClick={((rowData, event) => {navigate('data/');})} to onRowClick={((rowData, event) => {let id = event.sample_ID; navigate('data/' + id)})}; I was thinking the problem too complicated.
Thanks you guys for sharing!

Lazy load components in react

I'm working on a legacy react-app, hence lot of pieces cannot be reasoned about but simply accepted.
So, I have a couple of components that load a lot of dependencies and are obviously not important for the first render. Hence, I tried the following:
const HeavyComp = lazy(() => import("HeavyComponent.jsx");
function Home() {
return <div>
<HeavyComp />
</div>
}
As a result of this, HeavyComponent is loaded as part of main bundle and but is only visible after that component is loaded. This helps by breaking scripting time but FCP is still far away.
So, I tried the following:
function Home() {
const [ heavyComponent. setHeavyComponent ] = useState(null);
useEffect(() => {
setHeavyComponent(
lazy(() => import("HeavyComponent.jsx")
);
}, []);
return <div>
{
heavyComponent && <heavyComponent />
}
</div>
}
I thought this'd help but same as before, FCP was still delayed until heavyComponent was downloaded, parsed and rendered. So my only option was to make it async using setTimeout or better requestIdleCallback.
Is this the best solution or is there something better?
Assuming that with FCP you are referring to "first content paint". The best option is to use the Suspense component. With it, you can add a fallback loader component (<Spinner /> in this example).
import { Suspense, lazy } from 'react';
const HeavyComp = lazy(() => import("HeavyComponent.jsx");
function Home() {
return <div>
<Suspense fallback={<Spinner />}>
<HeavyComp />
</Suspense>
</div>
}
React concurrent-mode documentation

Individual loading animation for each page with Next.js

I want each of my pages to have different loading animations when loading. How can i achieve this?
It is not possible to put the loading component on the page component like this:
//Page component
Page.Loader = SomeLoaderComponent
//_app.tsx
const Loader = Component.Loader || DefaultLoader;
This will not work because "Component(the page)" isnt loaded/rendered yet.
I have also tried dynamic import with next.js, so that i can import the correct page based on the url, and then get the correct loader. My initial plan was to add the Loader to the page component, as shown at the first line in the code above. That does not work because I have to give an explicit path.
const getLoader = (pagePath: string): ComponentType => {
const Loader = dynamic(() => import(pagePath));
return Page.Loader;
};
This is stated in the Next.js docs:
So the question is: How can I get a custom loader per page?
You can use Suspense and lazy to accomplish your task.
lets say you have ComponentA.js as follows
const ComponentA = () => {
return <div>Helloooo</div>
}
You can create another component WrapperA.js file
import React, { Suspense } from 'react';
const WrapperA = React.lazy(() => import('./ComponentA'));
function WrapperA() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<ComponentA />
</Suspense>
</div>
);
}
in the place of <div>Loading...</div> you can have any loader component you like. and export WrapperA to your routes or tab as needed.

React Router redirect hash link

I have created a custom button component for my website's navbar. When the user clicks on a button, the component returns a Redirect, which takes the user to the page they selected.
export default class Button extends Component {
constructor(props){
super(props);
this.state = {redirect:false};
this._handleClick = this._handleClick.bind(this);
}
_handleClick(e) {
e.stopPropagation();
this.setState({redirect: true});
}
componentDidUpdate() {
if (this.state.redirect){
this.setState({redirect:false});
this.props.onRedirect();
}
}
render() {
if (this.state.redirect){
return <Redirect push to={this.props.dest}/>;
}
else {
return (
<li className="button" onClick={this._handleClick}>
<h5>{this.props.text}</h5>
</li>
);
}
}
}
Now, I'd like to add buttons that correspond to different sections of the same page. The simplest way I know of is to use hash links. One example of an address the button would redirect to is:
/home#description
However, React Router does not support doing this out of the box. I looked through a number of packages which add this functionality, such as react-router-hash-link and react-scrollchor. None of these however work with redirects, instead relying on Link or on custom components.
How do I go about adding this functionality to the buttons?
you could update window.location.href since it won't trigger a page refresh.
e.g.
window.location.href = '#your-anchor-tag';
One solution that I can think of is to use HOCs and hooks. The end result:
You'll get your app to scroll to the specified location...
without really needing to create custom buttons/links and...
without making much changes to your existing screens (Eg: HomeScreen)
Bonus: Users can copy, share & use URLs that will automatically scroll to the intended section
With assumption that the code below are pseudocode (they are based on my knowledge and not tested) and assuming there's a HomeScreen component, I would attempt adding <Route/>s to the <Switch/> inside the <Router/>.
<Switch>
<Route to='/home/:section' component={HomeScreen} />
<Route to='/home' component={HomeScreen} />
</Switch>
Then:
function withScrollToTarget(WrappedComponent) {
class WithScroll extends React.Component {
componentDidMount() {
const { match: { params: { section } } } = this.props
// Remember we had 2 <Route/>s, so if `section` is provided...
if (section) {
const scrollToTarget = document.getElementById(section)
// And just in case the item was removed or there was an ID mismatch
if (scrollToTarget) { scrollToTarget.scrollIntoView() }
}
}
render() { return <WrappedComponent {...this.props} /> }
}
return WithScroll
}
function useScrollToTarget(section) {
useEffect(() => {
if (section) {
const scrollToTarget = document.getElementById(section)
if (scrollToTarget) { scrollToTarget.scrollIntoView() }
}
}, [section])
}
Usage:
<nav>
<Link to='/home'>{'Home'}</Link>
<Link to='/home/description'>{'Description'}</Link>
</nav>
class HomeScreen extends React.Component { /* ... */ }
export default withScrollToTarget(HomeScreen)
// or
function HomeScreen() {
const { params: { section } } = useMatch() // from react-router-dom
useScrollTotarget(section)
return (
<div>
<h1 id='introduction'>Introduction</h1>
<h1 id='description'>Description</h1>
</div>
)
}
TLDR:
The route for '/home/:section' must be on top of '/home'. If the opposite, every time when <Switch/> compares the current URL against to, it will evaluate to true upon reaching '/home' and never reach '/home/:section'
scrollIntoView() is a legit function
If this works for you, you should look up on how to forward refs and hoisting statics in HOCs too
Who said React Router doesn't support this out of the box! You don't need those packages. You can redirect a hash i'll give you an example using the React-Router Route.
<Route
exact
path="/signup"
render={props => {
if (props.location.hash === "#foo")
return <Redirect push to="signup#bar"
return <Signup />
}}
/>
Now your version may not have supported this now that I think about it, but let me know if this helps :)
Happy coding!
React-hash-link should work for your redirect use case.
You can add <HashLinkObserver /> to your component tree and it will listen for hash links and scroll accordingly rather than relying on Link or custom components.
I think you should use the react-router-dom.
yarn add react-router-dom
Now update Custom Button Component like this
import React from 'react';
import { withRouter } from "react-router-dom";
class Button extends Component {
constructor(props){
super(props);
this.state = {redirect:false};
this._handleClick = this._handleClick.bind(this);
}
_handleClick(e) {
e.stopPropagation();
this.setState({redirect: true});
}
componentDidUpdate() {
if (this.state.redirect){
this.setState({redirect:false});
//this.props.onRedirect();
this.props.history.push('new uri');
}
}
render() {
if (this.state.redirect){
return <Redirect push to={this.props.dest}/>;
}
else {
return (
<li className="button" onClick={this._handleClick}>
<h5>{this.props.text}</h5>
</li>
);
}
}
}
export default withRouter(Button);
I was trying to solve a similar but slightly different issue, I want to deprecate an old hash route in favor of a new one. The posts here helped me arrive to my eventual solution:
<Route
exact
path={'/thing/:id'}
render={({
match: {
params: { id },
},
}) => (
<Redirect
push
to={`/newThing/${id}`}
/>
)}
/>
I was facing the same issue, I have created HOC to handle hash redirection, you can follow the below steps to achieve a hash redirection
create HOC and add below code to it
fileName : hashComponent
import React, { useEffect } from 'react';
export default function hashComponent(WrappedComponent) {
return function () {
const { pathname, hash }=window.location;
useEffect(() => {
if(hash)
window.location.href=`${pathname}${hash}`;
}, [hash])
return <WrappedComponent />
}
}
import your HOC in the component to which you want to handle hash URL
Then add below line of code while exporting your component
export default hashComponent(YourComponentName)

Categories

Resources