How to implement ScrollToTop in Material UI - reactjs

I followed the documentation on how to implement ScrollToTop with React Router but I am unable to get the function to work. I created a component ScrollToTop.js with the following code:
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;
}
In my App.js I add the wrapper after Router but all I get is a white screen whenever I call ScrollToTop. Here is a snippet from App.js.
<Router history={createBrowserHistory()}>
<AuthProvider>
<GlobalStyles />
<GoogleAnalytics />
<CookiesNotification />
<ScrollToTop>
{renderRoutes(routes)}
</ScrollToTop>
</AuthProvider>
</Router>
I am told to wrap <ScrollToTop around <App /> but that is not located in App.js but instead inside index.js in the root directory.

Your ScrollToTop component does its job. I've set up a working example here.
I have not changed ScrollToTop at all. The app looks like this:
function App() {
return (
<Router>
<ScrollToTop />
<Routes />
<Links />
</Router>
);
}
Now anytime the Router's pathname changes, the app will scroll to the top.

Related

React Router Scroll to Top on V6

When I change pages, the application is being kept at the same point it was on the previous page. I want to show the component from the top when I change pages. To achieve that, I am trying to implement React Router ScrollToTop.
I found the documentation and implemented it, but I am using react router v6, so it is a bit different.
https://v5.reactrouter.com/web/guides/scroll-restoration
Everything inside the ScrollToTop component doesn't get rendered, and I end up with a blank page.
App.js:
import { Router, Routes, Route } from "react-router-dom";
import './App.scss';
import Main from './pages/Main';
import Projects from './pages/Projects';
import NavBar from './components/NavBar';
import Footer from './components/Footer';
import ScrollToTop from './components/scrollToTop';
function App() {
return (
<div className="app" id="app">
<NavBar />
<div className='app-body'>
<Router>
<ScrollToTop>
<Routes>
<Route path="/portfolio" element={<Main />} />
<Route path="/portfolio/projects" element={<Projects />} />
</Routes>
</ScrollToTop>
</Router>
</div>
<Footer />
</div>
);
}
export default App;
ScrollToTop.js:
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;
}
As others have pointed out, you are wrapping your Routes component with the ScrollToTop component, but instead of editing it to render its implicit children prop I suggest converting it to a React hook, especially considering since it doesn't actually render anything, you want it to run as a side-effect of navigation.
function useScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
}
...
function App() {
useScrollToTop();
return (
<div className="App">
<div className="app-body">
<NavBar />
<Routes>
<Route path="/portfolio" element={<Main />} />
<Route path="/portfolio/projects" element={<Projects />} />
</Routes>
</div>
</div>
);
}
This necessarily requires you to lift the Router higher in the ReactTree to wrap the App component so it has a routing context to use for the useScrollToTop hook.
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Router>
<App />
</Router>
</StrictMode>,
rootElement
);
You put the Routes component as a descendant of ScrollToTop component, so you should return children instead of null.
ScrollToTop.js:
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default function ScrollToTop({ children }) {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return children;
}
The blank page is because you are returning null from <ScrollToTop> component. Instead if you return <></> or take the {children} prop and return that from <ScrollToTop >, it should work :)

Header component doesn't get updated after route change

I am trying to understand why Header component doesn't get updated when I click Button.
I believe that the problem is that I am not calling with Router. But why then App.js doesn't re render when I switch routes?
import React, { useEffect } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import First from './First';
import Second from './Second';
import Third from './Third';
import Header from './Header';
function App() {
return (
<div>
<Header />
<Router>
<Switch>
<Route exact path={'/'} component={First} />
<Route exact path={'/first'} component={Second} />
<Route exact path={'/second'} component={Third} />
</Switch>
</Router>
</div>
);
}
export default App;
import React from 'react';
export default function First(props) {
console.log('🚀 ~ file: First.js ~ line 4 ~ First ~ props', props);
return (
<div>
First
<button
onClick={() => {
props.history.push({
pathname: '/second',
});
}}
>
Go to Second
</button>
</div>
);
}
so my condition here doesn't get fired when path changes. the reason is that component hasn't been called and old condition is still there
import React from 'react'
export default function Header() {
console.log(window.location.pathname);
const logger = window.location.pathname === '/third' ? (<div>This is second</div>) :
(<div>this is root</div>)
return logger
}
I know that I can call Header somewhere else, but what is problem in this showcase?
The Header component is being rendered outside the Router, so it's not rerendered or made aware of route changes.
I suggest moving the Header component into the Router and have it access the route props.
App
function App() {
return (
<div>
<Router>
<Header /> // <-- move into Router
<Switch>
<Route exact path={'/'} component={First} />
<Route exact path={'/first'} component={Second} />
<Route exact path={'/second'} component={Third} />
</Switch>
</Router>
</div>
);
}
Header
import { useLocation } from 'react-router-dom';
export default function Header() {
const location = useLocation();
console.log(location.pathname);
return location.pathname === '/third'
? <div>This is second</div>
: <div>this is root</div>;
}
Alternatively you could use the useRouteMatch hook:
import { useRouteMatch } from 'react-router-dom';
export default function Header() {
const match = useRouteMatch('/third');
return match
? <div>This is second</div>
: <div>this is root</div>;
}

React routeer is causing a blank index page. Next JS

I'm creating a website, and I want the users to be directed to a specific page when they open the site. The page they are going to be directed to depends on if they already logged in. My problem is: the router doesn't work (user is not redirected to any page) and all that appears is a blank page. I've tried to get rid of the routes, but even though, I couldn't display anything on the index page. Maybe the problem is not even the router, but something else.
I never get any error messages. Here are the parts of the code, where I think the problem may be.
_app.js:
import React from 'react'
import { BrowserRouter as Router, Route } from "react-router-dom"
import Novidades from './lancamento'
import SignUp from './signup'
import Croosa from './croosa'
import { AuthProvider } from '../utils/auth'
import PrivateRoute from '../utils/PrivateRoute'
const App = () => {
return(
<AuthProvider>
<Router>
<div>
<PrivateRoute exact path='/lancamento' component={Lancamento} />
<Route exact path='/croosa' component={Croosa}/>
<Route exact path='/signup' component={SignUp} />
</div>
</Router>
</AuthProvider>
)
}
export default App
index.js:
import React from 'react'
import App from './_app'
export default function Home() {
return(
<App/>
)
}
And the PrivateRoute.js, which decides to which page the user is going to be redirected:
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthContext } from "./auth";
const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
const {currentUser} = useContext(AuthContext);
return (
<Route
{...rest}
render={routeProps =>
!!currentUser ? (
<RouteComponent {...routeProps} />
) : (
<Redirect to={"/signup"} />
)
}
/>
)
}
export default PrivateRoute
I would appreciate it if someone could point out my mistake(s).
Next.js uses a filesystem based routing structure.
You have a misunderstanding of how the _app.js file works. It's the parent component that is responsible for rendering the other components that get exported from other pages.
For example: if my _app.js file looks like this:
export default function App({ Component, pageProps }) {
return (
<div>
<p>This was injected by _app.js</p>
<Component {...pageProps} />
<div>
);
}
and my pages/index.js file looks like this:
export default function Hello(){
return <h1>Hello World!</h1>
}
With that setup if I visit the localhost:3000/ then the following will get rendered
<div>
<p>This was injected by _app.js</p>
<h1>Hello World!</h1>
</div>
What you did instead is in your _app.js you ignored the Component property that was passed and so on every page you visit the same content will be rendered. That is:
<AuthProvider>
<Router>
<div>
<PrivateRoute exact path="/lancamento" component={Lancamento} />
<Route exact path="/croosa" component={Croosa} />
<Route exact path="/signup" component={SignUp} />
</div>
</Router>
</AuthProvider>
The reason your index page is blank is because you didn't set up a route for / and so no component will be rendered by react router on that page. Regardless I suggest you stop using react router and start using the built in routing system with next.js

What's the right location to place "ScrollToTop" component?

I'm following the second answer to implement a scroll to top every time there's a route change. The answer suggests creating a new component, <ScrollToTop />, which I did
import { useEffect } from 'react';
import { withRouter } from 'react-router-dom';
function ScrollToTop({ history }) {
useEffect(() => {
const unlisten = history.listen(() => {
window.scrollTo(0, 0);
});
return () => {
unlisten();
}
}, []);
return (null);
}
export default withRouter(ScrollToTop);
I'm confused as to where to place such component, since my app is structured a bit differently. See the index.js and App.js files below.
index.js
ReactDOM.render(
<>
<EWLoader />
<ContextProvider>
<Router> //tried placing below <Router>, didn't work
<App />
</Router>
</ContextProvider>
</>,
document.getElementById('root'),
)
App.js
function App() {
return (
<> //tried placing above <Switch>, didn't work
<Switch>
<Route path="/EWadmin">
<Admin />
</Route>
<Route path="/ewadmin-allbookings">
<AdminAllBookings />
</Route>
<Route path="/">
<EWHeader />
<Middleware />
{/* <EWPopular /> */}
</Route>
</Switch>
</>
)
}
I've also noticed <ScrollToTop /> is destructuring an object's property history but I'm not importing or declaring it anywhere in index.js or App.js. Perhaps something should be done with regards to that?
There are two ways to achieve scrolling to top on page change:
First solution
Modify your ScrollToTop component to this:
function ScrollToTop({ history }) {
useEffect(() => {
const unlisten = history.listen(() => window.scrollTo(0, 0));
return unlisten;
}, [history]); // make sure to list history as a dependency
return null;
}
export default withRouter(ScrollToTop);
Then add ScrollToTop just under your router. Now it scrolls to top anytime the history changes, for any page.
Second solution
Modify your ScrollToTop component:
function ScrollToTop() {
useEffect(() => {
window.scrollTo(0, 0);
}, []); // No dependency
return null;
}
// No need to use withRouter
Then add ScrollToTop under every Route component where you want this behaviour.

React-router Link changes URL but doesn't rerender

I'm using MobX to render some orders with React and React-router. When I'm using Link the URL changes, but the view doesn't get rendered. When I refresh the page, the view is as it should be. I searched for a while and found here that MobX observer blocks rerendering because it uses shouldComponentUpdate. I tried using the location method, because that prop changes and should trigger a re-render, but that doesn't seem to help.
I also tried to use withRouter, but I didn't manage to implement that as a decorator.
Update
In this thread it's said that the decorators are breaking react-router? Using withRouter fixes it for them, but still I do not know how I should implement this into my code...
index.js
ReactDOM.render((
<Provider {...stores}>
<BrowserRouter>
<App location={location} />
</BrowserRouter>
</Provider>
),
document.getElementById('root')
);
App.js
import React from 'react';
import {Switch, Route, withRouter} from 'react-router-dom';
import {Order} from './components';
import {AllOrders} from './containers';
import {inject, observer} from 'mobx-react';
import {isEmpty} from 'lodash';
const App = ({orders, location}) => {
console.log(location);
return(
<Switch>
{
!isEmpty(orders) && <Route exact path="/orders" render={(props) => (<AllOrders {...props} orders={orders} />)} />
}
{
!isEmpty(orders) && <Route path="/orders/:id" render={(props) => (<Order {...props} orders={orders} />)} />
}
</Switch>
)
}
export default inject(
({orderStore}) => ({
orders: orderStore.orders
})
)(
observer(App)
);
AllOrders.js
class AllOrders extends Component{
render(){
const {orders} = this.props;
return(
<div>
{
!isEmpty(orders) &&
orders.map((props, key) => {
return(
<Link to={`/orders/${props.id}`} key={key}>{props.id}<br /></Link>
)
})
}
</div>
)
}
}
export default AllOrders;
Inside my BrowserRouter I added the component App, instead it should've been a Route which is passed the component App. This made the routing work again. The solution was changing my index.js to
ReactDOM.render((
<Provider {...stores}>
<BrowserRouter>
<Route component={App} />
</BrowserRouter>
</Provider>
),
document.getElementById('root')
);

Resources