React Router calls component constructor when switching routes - javascript

I'm using react and react-router for a SPA which has a navigation. The router is calling the react component 'constructor' every time a navigation item is switched. Here is the relevant bit of code:
class Home extends React.Component {
constructor(props) {
super(props);
console.log('component constructed!');
this.state = {
counter: 0
}
setInterval(() => this.setState({
counter: this.state.counter+1
}), 1000)
}
render() {
return (
<h3>Counter: {this.state.counter}</h3>
);
}
}
ReactDOM.render(
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="profile" component={Profile} />
<Route path="settings" component={Settings} />
</Route>
</Router>,
document.getElementById('container')
);
Every time I switch from one tab to another, the counter starts from 0.
Obviously I understand that render() should be called every time its state changes or when router switches tabs, but why call constructor for change of tab?! Is that how react-router works, or am I doing something wrong?
This question has been asked here, but the accepted answer talks about 're-rendering' and not re-construction of the component.

The constructor is called every time that a component mounts. Each time that you navigate to the location /, the <Home> component will mount. When you navigate to another location, the <Home> component will unmount. If you want the state to be persistent across navigation, it should be kept higher up the tree.

Related

Avoid re-rendering of parent components with react-router-dom v6 nested routes

In the process of upgrading my react app to react-router-dom v6, I had to refactor how the nested routing is handled. In the previous versions of react-router-dom, I managed to declare nested routes in child components, and that avoided re-rendering the parent component every time the route changed in the app.
The configuration could be summarized as something like this:
const Component = React.lazy(() => import('./path/to/Component'));
<Switch>
<Route path="..." component={Component} />
</Switch>
Then, inside the Component:
const NestedComponent = React.lazy(() => import('./path/to/NestedComponent'));
const Component = ({match}) => {
...
<Route path={match.url} component={NestedComponent} />
...
}
Following the upgrade guide for react-router-dom v6, I refactored the components above into something as the following:
const Component = React.lazy(() => import('./path/to/Component'));
<Routes>
<Route path=".../*" element={
<React.Suspense fallback={"loading 1..."}>
<Component />
</React.Suspense>
} />
</Routes>
Then in the component:
const NestedComponent = React.lazy(() => import('./path/to/NestedComponent'));
const Component = ({match}) => {
...
<Routes>
<Route path={match.url} element={
<React.Suspense fallback={"loading 2..."}>
<NestedComponent />
</React.Suspense>
} />
</Routes>
...
}
What I expected would have been that the Component component would not re-render while changing the route, only the NestedComponent (hence, showing only loading 2... inside the Component). But instead, when I change the route, all the components re-render. Also, I'm not sure whether with the new syntax (using element instead of component in the Route component) makes sense combined with React.lazy.
Is there a way to avoid the re-rendering of the parent component?
With the new syntax, does it make sense to use React.lazy? Or is there another way to lazily load components?

Should I be using functions instead of classes for pages in ReactJS?

I've noticed React Router DOM (v6) is now using functions in their help guides in regards to pages instead of using classes.
Previously used classes like so:
export class Login extends React.Component {
render() {
return <p>test</p>
}
}
However, after trying the latest version of React Router DOM I would receive an empty page for the navigation route.
import {Login} from "./js/components/Auth/Login";
function App() {
return <h2>Test</h2>
}
if (document.getElementById('app')) {
ReactDOM.render(
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/login" component={() => <Login />} />
</Routes>
</BrowserRouter>, document.getElementById('app'))
}
App works fine ('/' route), but /login returns an empty page with no error..
Login refers to a class that uses render() and returns <p>test</p>
Am I doing something incorrectly? Are libraries leaning towards functions instead of class components?
This guide seems to be only using functions for components.
https://reactrouter.com/docs/en/v6/getting-started/tutorial
Class vs Function components is irrelevant in RRDv6, they simply render React components specified as JSX, not as a reference to a component, and not as a function returning JSX. There is also no render or component props, the Route components now use only element to render the route components.
<Routes>
<Route path="/" element={<App />} />
<Route path="/login" element={<Login />} />
</Routes>

reactjs props getting lost in Route creation

I'm attempting to pass a user's auth state down to components via a react-router-dom switch block (I believe I ought to look at implementing redux but that's a question for later).
There's a Home view that gets passed all the login information after the user authenticates, and I can see the authState object as a prop in the home component using react devtools:
import React from "react";
import Dashboard from "../Dashboard";
import {Switch, Route} from "react-router-dom";
import NoMatch from "../NoMatch";
function Home(props) {
// authState exists
return (
<Switch {...props}>
// authState exists
<Route exact path="/" render={(...props) => <Dashboard {...props} />} /> // authState gone
<Route component={NoMatch} />
</Switch>
);
}
export default Home;
after executing this it renders the Dashboard, and tracing down the component chain with react devtools I can see that the prop has been passed from the Home component to the Switch component successfully. However, once it gets to the Route the prop is gone, and the only props available in the Dashboard component are the history, location and match props.
Why are the props missing and what's the right way to pass them down?
Couple of improvements needed in your code:
Passing props to Switch component is unnecessary
No need to collect router props using the rest syntax only to spread them later
Main Problem:
props inside the function passed to render prop refers to the router props instead of the props passed to Home component. You are using the same identifier for two different things.
Solution
Use different names for router props and the props passed to Home component that you want to pass down to Dashboard component
function Home(props) {
return (
<Switch>
<Route
exact
path="/"
render={(routerProps) => <Dashboard {...routerProps} {...props} />}
/>
<Route component={NoMatch} />
</Switch>
);
}
Alternatively, if you don't need access to router props in the Dashboard component, remove the props parameter from the render prop function.
<Route
exact
path="/"
render={() => <Dashboard {...props} />}
/>
Now, you won't have access to router props inside the Dashboard component but the auth state of the user will be passed down to Dashboard component.
In the most recent versions of the react-router-dom you must replace render and component attributes with element. You cannot pass a callback function there in which there were specified the route props anymore.
Instead you can use a hook in your routed component:
const params = useParams();
to obtain the parameters.
See more in the official documentation.

React Switch not rendering different pages

I got a react app that got a component that renders my HomePage. I decided to create another component so i can switch between pages on the site. For the HomePage it works when i enter the site, but clicking on navlinks is not working, also if i type the url /contacto i am not getting the render of Contact component, i get the render of the HomePage component, it's not switching between them.
class ContactPage extends Component {
render() {
return <div>HOLA</div>;
}
}
function App() {
return (
<BrowserRouter>
<Switch>
<Route path="/" component={HomePage} />
<Route path="/contacto" component={ContactPage} />
</Switch>
</BrowserRouter>
);
}
Add exact to your HomePage route:
<Route exact path="/" component={HomePage} />
This is because the path / still matches in a non exact way to /contacto and the router renders the first match (that being your HomePage).
By adding exact, you're telling it not to match partial matches.

Render child component over parent

I need to render component with another route but this component must not cover all page. For example, I clicked on some question from stackoverflow list, and than I will receive animate from right to left modal, and I need to change route also
React router (I am using V4)
export default (
<Switch>
<App>
<Route exact={true} path="/" component={App} />
<Route exact={true} path="/product/:id" component={Product}/>
</App>
</Switch>
);
My product container looks like
export default function productContainer(ChildComponent) {
class ProductContainer extends Component {
render = () => {
return <ChildComponent/>
}
}
return ProductContainer;
}
And my product component
class Product extends Component {
render = () => {
return ("")
}
}
export default productContainer(Product);
When I emulate situation, which I describe above, my page fully rerendred and I don't see my App component Page
Have any idea, how I can resolve this issue?
People have asked how to render a modal as a route before in react-router without re-rendering (can't find the discussion right now). Essentially with react-router this is not possible. Each route change causes a re-render. That said, you can do what you want by nesting your routes.
Each component can return routes, so by using composition you can choose where to render any route.
For instance,
export default (
<Switch>
<App>
<Route path="/" component={App} />
</App>
</Switch>
);
Inside <App /> -
render() {
return (
<Something>
<PageHeader />
<Switch>
<Route path="/product/:id" component={Product}/>
</Switch>
</Something>
);
}
So you can see, if you were to add your routes inside the App component, they can all share a common page layout.
Remember: Any component can return multiple routes or just a single one inside a Switch! Switch will only render the first route that matches.
Also Remember: Routes inside a switch must be a direct child, you can't have Switch -> App -> Route, it must always be a direct child like Switch -> Route
Use render property instead of component in Route.
<Route exact={true} path="/" render={() => (
<div>
<App />
<Route exact={true} path="/product/:id" component={Product}/>
</div>
)} />

Categories

Resources