React Router v5 - Clicking <Link /> is not re-rendering - reactjs

As I understand, clicking on React Router's <Link /> component should cause everything inside <Router /> to re-render.
However, it seems like that is not the case in this simple example app using React Router DOM v5.2:
import React from "react";
import "./styles.css";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
const Foo = () => {
console.log("rendered");
return null;
};
export default function App() {
return (
<Router>
<Foo />
<div className="App">
<Link to="/">Home</Link>
<br />
<Link to="/foo">Foo</Link>
<Route path="/" exact>
<p>Home</p>
</Route>
<Route path="/foo">
<p>Foo</p>
</Route>
</div>
</Router>
);
}
https://codesandbox.io/s/vigorous-water-2fuxt?file=/src/App.js
What am I missing?

If you actually render Foo on a route with path then it rerenders when that path is matched. A route without a path will always match and be rendered, so it is rendered when it mounts and doesn't rerender since it has no props nor any state to update (it would if the component containing the Router remounts/updates).
Renders once per render of Router
<Route>
<Foo />
</Route>
Rerenders once per path match
<Route path="/foo">
<Foo />
</Route>
Consider this demo
const Foo = () => {
console.log("rendered Foo");
return null;
};
const Bar = () => {
console.log("rendered Bar");
return null;
};
export default function App() {
const [c, setC] = useState(0);
return (
<Router>
<Route>
<Foo />
</Route>
<button onClick={() => setC(c => c + 1)}>Rerender Router</button>
<div className="App">
<Link to="/">Home</Link>
<br />
<Link to="/bar">Bar</Link>
<Route path="/" exact>
<p>Home</p>
</Route>
<Route path="/bar">
<Bar />
</Route>
</div>
</Router>
);
}

As I understand, clicking on React Router's <Link /> component should cause everything inside <Router /> to re-render.
not <Router /> but <Route /> only if the specified path matches the current path.
the children of <Router /> will only be rendered once.

Related

React Router V5 How to have links to nested and parent router? Adding Redux messes with Routing

I have a page which uses React Router V5. There is a general section and there is a section specifically for user profiles. As I have a different page structure there, I used nested routes for the account section. The SideBar is what the name implies and contains buttons which can be used to navigate the account pages /account/profile, /account/settings, as well as navigate to pages outside the nested switch - namely /help.
The App used to be structured roughly like this:
// in index.js
const appDiv = document.getElementById("app")
render(<App />, appDiv)
// in App.js
const App = () => {
return (
<Router>
<div className={styles.pageContainer}>
<Header />
<Switch>
<Route exact path='/' component={() => <HomePage link='about' />} />
<Route path='/about' component={AboutPage} />
<Route path='/help' component={HelpPage} />
<Route path='/log-in' component={LoginPage} />
<Route path='/sign-up/:signupType' component={SignUpPage} />
<Route path='/account' component={AccountRouter} />
</Switch>
<Footer />
</div>
</Router>
}
// in AccountRouter.js
const AccountRouter = () => {
return (
<div className={styles.container}>
<SideBar />
<Switch>
<Route path='/account/settings' component={AccountSettingsPage} />
<Route path='/account/profile' component={ProfileSettingsPage} />
<Route exact path='/account' component={ProfileSettingsPage} />
</Switch>
</div>
);
};
// in SideBar.js
const SideBar = () => {
const history = useHistory();
return (
<div>
<button onClick={() => history.push('/account/profile')}>Go to Account Profile</button>
<button onClick={() => history.push('/account/settings')}>Go to Account Settings</button>
<button onClick={() => history.push('/help')}>Go to Help Page</button>
</div>
)
}
Now it is structured like this:
// in index.js
const appDiv = document.getElementById("app")
render(
<Provider store={store}>
<App />
</Provider>
, appDiv)
// in App.js
// This looks the same
// in AccountRouter.js
// This now has Route protection
const AccountRouter = () => {
const accountData = useSelector((state) => state.account);
return (
<div className={styles.container}>
{!accountData.isLoggedIn ? <Redirect to='/log-in' /> : null}
<SideBar />
<Switch>
<Route path='/account/settings' component={AccountSettingsPage} />
<Route path='/account/profile' component={ProfileSettingsPage} />
<Route exact path='/account' component={ProfileSettingsPage} />
</Switch>
</div>
);
};
// in SideBar.js
// This looks the same.
Before I added Redux, the Sidebar was properly redirecting.
After Redux, I have the following behaviour:
When the SideBar is outside of the Switch, you can properly navigate to the Help page, but the components don't render, when I try to navigate to the pages inside the AccountRouter Switch. When I move the SideBar into the Switch, the links to the pages inside this switch start working again, but the /help link stops working.
Is there a way of having links to both inside and outside of this Switch in the same SideBar? How could Redux have affected the Router?

Why is rendering the parent component and the child trying to enter the child component

Why is rendering the parent component and the child trying to enter the child component
"react-router-dom": "^6.0.1",
when I enter on the route:
http://localhost:3000/dashboard- the view work
http://localhost:3000/dashboard/employee - rendering dashboard and employee view (both views)
http://localhost:3000/dashboard/accounting - rendering dashboard and accounting view (both views)
Documentation:
https://reactrouter.com/docs/en/v6/getting-started/tutorial#nested-routes
index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
App.js
import AppRouter from "./routers/AppRouter";
function App() {
return (
<>
<AppRouter />
</>
);
}
export default App;
AppRouter.js
import { Route, Routes } from "react-router-dom";
import Navbar from "../components/template/Navbar";
import AccountingHomeView from "../components/views/accounting/AccountingHomeView";
import DashboardHomeView from "../components/views/dashboard/DashboardHomeView";
import EmployeeHomeView from "../components/views/employee/EmployeeHomeView";
import HomeView from "../components/views/public/HomeView";
import LoginView from "../components/views/public/LoginView";
const AppRouter = () => {
return (
<div>
<Navbar />
<Routes>
<Route path="/" element={<HomeView />} />
<Route path="dashboard" element={<DashboardHomeView />}>
<Route path="employee" element={<EmployeeHomeView />} />
<Route path="accounting" element={<AccountingHomeView />} />
</Route>
<Route path="/login" element={<LoginView />} />
</Routes>
</div>
);
};
export default AppRouter;
DashboardHomeView.js (with outlet)
import { Outlet } from "react-router-dom";
const DashboardHomeView = function () {
return (
<>
<h1>DashboardHomeView</h1>
<Outlet />
</>
);
};
export default DashboardHomeView;
component children Accounting
import React from "react";
const AccountingHomeView = function () {
return (
<div>
<h1> Accountin</h1>
</div>
);
};
export default AccountingHomeView;
I also initially found this a bit confusing, but with nested routes the "parent" route is considered more of a "layout" component in that it is always rendered when its path matches, and renders all its children routes into its outlet.
const AppRouter = () => {
return (
<div>
<Navbar />
<Routes>
<Route path="/" element={<HomeView />} />
<Route
path="dashboard"
element={<DashboardHomeView />} // <-- always matched/rendered at "/dashboard*"
>
<Route
path="employee"
element={<EmployeeHomeView />} // <-- conditionally matched/rendered
/>
<Route
path="accounting"
element={<AccountingHomeView />} // <-- conditionally matched/rendered
/>
</Route>
<Route path="/login" element={<LoginView />} />
</Routes>
</div>
);
};
const DashboardHomeView = function () {
return (
<>
<h1>DashboardHomeView</h1> // <-- always matched/rendered at "/dashboard*"
<Outlet /> // <-- conditionally matched/rendered children
</>
);
};
Nested-Routes
You may have noticed when clicking the links that the layout in App
disappears. Repeating shared layouts is a pain in the neck. We've
learned that most UI is a series of nested layouts that almost always
map to segments of the URL so this idea is baked right in to React
Router.
I believe what you are expecting is what is called an Index Route. It is what would be rendered on a "/dashboard" route when it isn't a layout/wrapper container.
Notice it has the index prop instead of a path. That's because the
index route shares the path of the parent. That's the whole point--it
doesn't have a path.
Maybe you're still scratching your head. There are a few ways we try
to answer the question "what is an index route?". Hopefully one of
these sticks for you:
Index routes render in the parent routes outlet at the parent route's path.
Index routes match when a parent route matches but none of the other children match.
Index routes are the default child route for a parent route.
Index routes render when the user hasn't clicked one of the items in a navigation list yet.
const AppRouter = () => {
return (
<div>
<Navbar />
<Routes>
<Route path="/" element={<HomeView />} />
<Route path="dashboard" element={<DashboardLayout />}>
<Route path="employee" element={<EmployeeHomeView />} />
<Route path="accounting" element={<AccountingHomeView />} />
<Route index element={<DashboardHomeView />} />
</Route>
<Route path="/login" element={<LoginView />} />
</Routes>
</div>
);
};
const DashboardLayout = function () {
return (
<div /* with any layout styling */>
.... other common layout content
<Outlet />
.... more possible common page content
</div>
);
};
const DashboardHomeView = function () {
return (
<>
<h1>DashboardHomeView</h1>
.... dashboard specific content
</>
);
};
How about using the exact prop for the parent Route. Like <Route exact path="dashboard" element={<DashboardHomeView />}>. This may solve the issue.

React execute common code on each route change

I have a component included in my App.js and that component contains some common logic which needs to be executed.
So my App component JSX looks like
<CommonComponent />
{canRenderBool && (
<div class="container">
<Route exact path="/comp1">
<Comp1 />
</Route>
<Route exact path="/comp2">
<Comp2 />
</Route>
</div>
)
}
Now, I want that on each route transition (e.g. user clicks on a new route url), I want that the code in CommonComponent (non-routable component) gets triggered.
This common component has logic which returns a boolean variable and I kind of render/not render Comp1/Comp2 i.e. all the routes based on that boolean
What is the best way I can handle it. I want to avoid having that code defined/invoked in each component manually?
Also most of my components are functional/hooks based.
In case you are using functional component for your App Component. You can write your CommonComponent logic inside useEffect and then there you can set state for your canRenderBool flag
for example
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Switch, Route, Link, useLocation } from "react-router-dom";
function App() {
const [canRenderBool , setCanRenderBool] = useState(false);
const location = useLocation(); // this will only work if you wrap your App component in a Router
// this effect will run on every route change
useEffect(() => {
// your logic here
// ..
// set value of 'canRenderBool' flag
setCanRenderBool(true);
}, [location]);
return (
// Note that Router is not used here it will be wraped when we export this component, see below
<Switch>
<Route exact path="/">
<Page1 />
</Route>
{canRenderBool && (
<Route exact path="/page-2">
<Page2 />
</Route>
)}
</Switch>
);
}
function Page1() {
return (
<div>
<h1>
Go to <Link to="/page-2">Page 2</Link>
</h1>
</div>
);
}
function Page2() {
return (
<div>
<h1>
Go to <Link to="/">Page 1</Link>
</h1>
</div>
);
}
// Wrapping Router around App and exporting
export default () => (
<Router>
<App />
</Router>
);
{canRenderBool && (
<Router>
<div class="container">
<Switch>
<Route exact path="/comp1"> <Comp1 /> </Route>
<Route exact path="/comp2"> <Comp2 /> </Route>
</Switch>
<Link to="/comp1">Component 1</Link>
<Link to="/comp2">Component 2</Link>
</div>
</Router>
)
}
This might help. And you will need to import all keywords and components before using them.

Cannot render child components with React Router (Nested Routes)

I am trying to use nested routes to render different components. When I click my links, URL does update but the components are not rendering. Prior to this I was using imported components, but since that wasn't working, I stripped it down to this block of code and it's still just showing a blank component and no errors.
import React from 'react';
import { Route, Switch, Link, useRouteMatch } from 'react-router-dom';
function InfluencerComponent() {
let { path, url } = useRouteMatch();
const navLinks = (
<div>
<Link to={`${url}/select-trade`}>Select trade</Link>
<Link to={`${url}/add-skills`} className="ml-2">
Add skills
</Link>
</div>
);
return (
<div className="row mt-3">
<Switch>
<Route exact path={path}>
{navLinks}
</Route>
<Route path={`${path}/select-trade`}>
{navLinks}
<Test />
</Route>
<Route path={`${path}/add-skills`}>
{navLinks}
<TestTwo />
</Route>
</Switch>
</div>
);
}
function Test() {
return 'Test Component';
}
function TestTwo() {
return 'Another Test Component';
}
export default InfluencerComponent;
Components are not rendering because you should use component prop instead of children.
Example:
return (
<div className="row mt-3">
<Switch>
// ...
<Route path={`${path}/add-skills`} component={<>{navLinks}<TestTwo /></>} />
</Switch>
</div>
);
More info about <Route /> props:
https://reacttraining.com/react-router/web/api/Route/component

How to implement nested Routing (child routes) in react router v4?

The component tree i want is as below
- Login
- Home
- Contact
- About
Contact and About are children of Home.
This is my App.js ,
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Route exact path="/home" component={HomeView} />
</div>
</BrowserRouter>
);
}
}
render(<App />, document.getElementById('root'));
This is Home,
export const HomeView = ({match}) => {
return(
<div>
<NavBar />
Here i want to render the contact component, (Navbar need to stay)
</div>
)
}
This is my Navbar,
export const NavBar = () => {
return (
<div>
<Link to="/home">Home</Link>
<Link to="/home/contact">Contact</Link>
<hr/>
</div>
)
}
Contact component just need to render "hello text".
To make nested routes you need to remove exact:
<Route path="/home" component={HomeRouter} />
And add some routes:
export const HomeRouter = ({match}) => {
return(
<div>
<NavBar />
{/* match.path should be equal to '/home' */}
<Switch>
<Route exact path={match.path} component={HomeView} />
<Route exact path={match.path + '/contact'} component={HomeContact} />
<Switch>
</div>
)
}
You don't need use match.path in nested routes but this way it will be easier to move everything from "/home" to "/new/home" in case you decide to change your routing.

Resources