React and antd: Router doesn't re-render components - reactjs

I have a simple web page with login and search pages. I also have a navbar at the top to allow for switching between the two. The basic App.js looks as follows:
const history = createBrowserHistory();
function App() {
return (
<Router history={history}>
<CustomLayout>
<Switch>
<BaseRouter/>
</Switch>
</CustomLayout>
</Router>
);
}
export default App;
Now, the BaseRouter and CustomLayout are just
const BaseRouter = () => (
<div>
<Route exact path={"/list"} component={ItemsList}/>
<Route path={"/login"} component={LoginForm}/>
</div>
);
export default BaseRouter;
and
const CustomLayout = ({children}) => {
return(
<>
<Navbar/>
{children}
</>
);
}
export default CustomLayout;
Now, the navbar looks like this
import React from "react";
import {Menu} from 'antd';
import {Link} from "react-router-dom";
const Navbar = () => {
return (
<div>
<Menu mode="horizontal" theme={"dark"}>
<Menu.Item key="list">
<Link to={"/list"}>List</Link>
</Menu.Item>
<Menu.Item key={"login"}>
<Link to={"/login"}>Sign in</Link>
</Menu.Item>
</Menu>
</div>
);
}
export default Navbar
Let's keep components simple:
const Login = () => {
return (
<div>
login
</div>
);
}
export default Login
const List = () => {
return (
<div>
list
</div>
);
}
export default List
Now the problem is that when I click on links in the navbar, React doesn't re-render components even though the route changes. I've seen hundreds of answers on SO but I still can't figure it out.
NOTE
It is important for me to avoid refreshing or reloading the page.
EDIT
Strangely enough, when I change Router to BrowserRotuer it works fine, but I can't use my own history then.

why don't you use BrowserRouter from react-router-dom package.
App.js:- use BrowserRouter from react-router-dom
import { BrowserRouter as Router, Switch } from 'react-router-dom'
function App() {
return (
<Router>
<CustomLayout>
<Switch>
<BaseRouter/>
</Switch>
</CustomLayout>
</Router>
);
}
export default App;
BaseRouter.js:- import Route from react-router-dom
import { Route } from 'react-router-dom'
const BaseRouter = () => (
<div>
<Route exact path="/list" component={ItemsList}/>
<Route path="/login" component={LoginForm}/>
</div>
);
export default BaseRouter;
Navbar.js:-
import React from "react";
import {Menu} from 'antd';
import {Link} from "react-router-dom";
const Navbar = () => {
return (
<div>
<Menu mode="horizontal" theme={"dark"}>
<Menu.Item key={"list"}>
<Link to="/list">List</Link>
</Menu.Item>
<Menu.Item key={"login"}>
<Link to="/login">Sign in</Link>
</Menu.Item>
</Menu>
</div>
);
}
export default Navbar
then if you want to use history:-
import { useHistory } from 'react-router-dom'
const testFunctionComponent = () => {
const history = useHistory()
const handleClick = (urlPath) => {
// you can do
history.push(urlPath) // to go anywhere
}
return (
<>
<button onClick={() => handleClick('/anypath')}>Click Me!<button>
</>
)
}

Change your BaseRouter from this :
const BaseRouter = () => (
<div>
<Route exact path={"/list"} component={ItemsList}/>
<Route path={"/login"} component={LoginForm}/>
</div>
);
export default BaseRouter;
To this :
const BaseRouter = () => (
<div>
<Route exact path="/list" component={ItemsList}/>
<Route path="/login" component={LoginForm}/>
</div>
);
export default BaseRouter;

I believe you cannot have the div inside the switch. You're not exposing the Route components to your switch statement.
Therefore, your url changes because your Navbar makes it change but your switch doesn't know what to do.
Try changing your base router to this:
const history = createBrowserHistory();
function App() {
return (
<Router history={history}>
<CustomLayout>
<BaseRouter/>
</CustomLayout>
</Router>
);
}
export default App;
const BaseRouter = () => (
<Switch>
<Route exact path={"/list"} component={ItemsList}/>
<Route path={"/login"} component={LoginForm}/>
</Switch>
);
export default BaseRouter;

Router is low-level and expects you to manage things. BrowserRouter already syncs with HTML5 history. If you want Router, I think you have to manage the syncing yourself (e.g. <Link onClick={() => history.push(href)}>) or listen to history for change detection. See Detect Route Change with react-router

Related

React fullpageJS and react-router-dom

I am trying to use react fullpageJS and react router dom to create a carousel but it shows empty screen, Here's the code I am using:
APP.JS:
import { Route, Routes } from "react-router-dom";
import Navigation from "./routes/navigation/navigation.component";
import Home from "./components/home/home.component";
function App() {
const About = () => {
return (
<div>
<h1>This is about</h1>
</div>
);
};
return (
<div>
<Routes>
<Route path="/" element={<Navigation />}>
<Route index element={<Home />} />
<Route path="/about" element={<About />} />
</Route>
</Routes>
</div>
);
}
export default App;
Home.jsx:
import { useState, useEffect, React } from "react";
import ProjectPreview from "../project-preview/project-preview.component";
import ReactFullpage from "#fullpage/react-fullpage";
// import "fullpage.js/vendors/scrolloverflow";
import PROJECTS_DATA from "../../Projects";
const Home = () => {
const [projectsToPreview, setProjects] = useState([]);
useEffect(() => {
setProjects(PROJECTS_DATA);
}, []);
<ReactFullpage
render={() => {
return (
<ReactFullpage.Wrapper>
<ReactFullpage.Wrapper>
{projectsToPreview.map((project) => {
return (
<div className="section" key={project.id}>
<h1>Test</h1>
<ProjectPreview project={project} />
</div>
);
})}
</ReactFullpage.Wrapper>
</ReactFullpage.Wrapper>
);
}}
/>;
};
export default Home;
The rendered screen shows only the navbar component but the content in the slider appear neither on the screen nor in the javascript dom
index.js:
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
reportWebVitals();
Navigation.jsx
import { Link, Outlet } from "react-router-dom";
import { Nav, Navbar } from "react-bootstrap";
const Navigation = () => {
return (
<>
<Navbar expand="lg" variant="dark">
<Link className="navbar-brand" to="/">
LOGO
</Link>
<Nav className="ms-auto">
<Link className="nav-link" to="/about">
About
</Link>
</Nav>
</Navbar>
<Outlet />
</>
);
};
export default Navigation;
I suspect the issue is that the Navigation component isn't rendering an Outlet component for the nested routes to render their element into.
Navigation should render an Outlet.
Example:
import { Outlet } from 'react-router-dom';
const Navigation = () => {
...
return (
... nav and layout UI
<Outlet /> // <-- nested routes render content here
...
);
};

How can I hide header component in Login page

I have a global header component inside my router. But I want to hide on the login page.
I tried to use window.location solution like this. It works but doesn't work after the login page navigates to the homepage. (it doesn't show header till I refresh the page)
App.js
import React, { useState, useEffect } from "react";
import "./sass/app.scss";
import { db, auth } from "./configs/firebase-config";
import { MainContext } from "./hooks/Context";
import { eventbriteRoutes } from "./configs/routes";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Header from "./components/Home/Header";
function App() {
const [isAuth, setIsAuth] = useState(localStorage.getItem("isAuth"));
const data = {
isAuth,
setIsAuth,
};
return (
<>
<MainContext.Provider value={data}>
<Router>
{window.location.pathname !== "/login" ? <Header /> : null}{" "}
<Routes>
{eventbriteRoutes.map((RouteItem, index) => (
<Route
exact
key={index}
path={RouteItem.path}
element={RouteItem.element}
/>
))}
</Routes>
</Router>
</MainContext.Provider>
</>
);
}
export default App;
Create a layout route that renders (conditionally) the Header component and an Outlet component for the nested route components.
Example:
import { Outlet, useLocation } from 'react-router-dom';
const Layout = ({ hideHeaderPaths = [] }) => {
const { pathname } = useLocation();
return (
<>
{!hideHeaderPaths.includes(pathname) && <Header />}
<Outlet />
</>
);
}
...
function App() {
const [isAuth, setIsAuth] = useState(localStorage.getItem("isAuth"));
const data = {
isAuth,
setIsAuth,
};
return (
<>
<MainContext.Provider value={data}>
<Router>
<Routes>
<Route element={<Layout hideHeaderPaths={["/login"]} />}>
{eventbriteRoutes.map((RouteItem) => (
<Route
key={RouteItem.path}
path={RouteItem.path}
element={RouteItem.element}
/>
))}
</Route>
</Routes>
</Router>
</MainContext.Provider>
</>
);
}
Or if it's easier to just separate the "/login" route you can just create a layout route that unconditionally renders the Header component and render the login route separately.
Example:
import { Outlet } from 'react-router-dom';
const HeaderLayout = () => (
<>
<Header />
<Outlet />
</>
);
...
function App() {
const [isAuth, setIsAuth] = useState(localStorage.getItem("isAuth"));
const data = {
isAuth,
setIsAuth,
};
return (
<>
<MainContext.Provider value={data}>
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route element={<HeaderLayout} />}>
{eventbriteRoutes.map((RouteItem) => (
<Route
key={RouteItem.path}
path={RouteItem.path}
element={RouteItem.element}
/>
))}
</Route>
</Routes>
</Router>
</MainContext.Provider>
</>
);
}
Could potentially just remove it from the app.js and include it on the pages you want for it to be on. Or add class to make it display none based on the route

history.push reloads the whole page even though the pathname is the same when using code-splitting

My initial question was
How to add url params without the whole page reloading?
#Ajeet Shah told me in the comments that the page shouldn't reload if the pathname is the same. So I figured the problem lies elsewhere. I could pin point the problem and found out it has to do with code splitting, I could even create a reproducible example here.
The problem is the following: When part of the code is loaded asynchronously and it contains routes, calls to history.push() with the exact same pathname make the page reload.
example.js
import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import AsyncComponent from "./components/AsyncComponent";
export default function BasicExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about/test">About</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<AsyncComponent />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
AsyncComponent.jsx
import React, { Suspense } from "react";
const Component = React.lazy(() => import("./Routes"));
export const AsyncComponent = (props) => {
return (
<Suspense fallback={<div>...loading</div>}>
<Component />
</Suspense>
);
};
export default AsyncComponent;
Routes.jsx
import React, { useEffect } from "react";
import { Switch, Route } from "react-router-dom";
import { useRouteMatch } from "react-router";
import About from "./About";
export const Routes = (props) => {
let { path } = useRouteMatch();
console.log("rendering Routes");
useEffect(() => {
console.log("mounting Routes");
}, []);
return (
<Switch>
<Route exact path={`${path}/test`}>
<About />
</Route>
</Switch>
);
};
export default Routes;
About.jsx
import React, { useEffect } from "react";
import { useHistory } from "react-router";
import { Link } from "react-router-dom";
export const About = () => {
console.log("rendering About");
useEffect(() => {
console.log("mounting About");
}, []);
const h = useHistory();
return (
<div>
<Link
to={{
pathname: "/about/test",
search: "foo=1&bar=2"
}}
>
Push search params to About
</Link>
<button
onClick={() => {
h.push({
pathname: "/about/test",
search: "foo=1&bar=2"
});
}}
>
Push search params to About
</button>
<h2>About</h2>
</div>
);
};
export default About;
What am I doing wrong? Is there a way to avoid this while keeping code-splitting?

Open modal in React Router

In my react project, this is my App.js:
import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Modal from "./Modal";
export default function BasicExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/modal">
<Modal />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
<p>
Please <Link to="/modal/1">Click Here</Link> for see details.
</p>
</div>
);
}
When you click on "Click Here", the modal was open, but my home page will be disappear. how can open this modal without destroying the home page ?
DEMO HERE:
https://codesandbox.io/s/react-router-basic-2g9t1
Modals should not be in a route as they are supposed to be on top of another page, not a page themshelves. If you want my opinion I would suggest you to put the modal in any of the pages and control if it is opened or not with a react state:
import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Modal from "./Modal";
export default function BasicExample() {
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home} />
</Switch>
</div>
</Router>
);
}
const Home = () => {
const [ isModalOpened, setModalOpened ] = useState(false);
return (
<div>
<h2>Home</h2>
<button onClick={() => setModalOpened(!isModalOpened)}
<Modal isOpened={isModalOpened}>
...modal content here
</Modal>
</div>
);
}
And your modal component should look like something like this:
const Modal = ({ isOpened, children }) => (
<div>
{
isOpened &&
{ children }
}
</div>
)
If this helps you make sure to mark it as a good response!

Second and subsequent Route in react-router-dom is not being rendered?

I have been working with react-router-dom v4 and i am trying to make a central config for the routes with a workaround.
This is my routes.js
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import {
WebsiteFilter,
WebsiteLeaderBoard,
WebsiteGraph,
WebsiteQues
}
from "./components";
import { DashboardApp ,LeaderBoardApp} from "./containers";
const dashboardContainer = (id) => (
<DashboardApp key={id}>
<Switch>
<Route exact path="/dashboard" render={(props) => (
<>
<WebsiteFilter />
<WebsiteGraph />
<WebsiteQues />
</>
)}
/>
</Switch>
</DashboardApp>
);
const leaderBoardContainer = (id) => (
<LeaderBoardApp key={id}>
<Switch>
<Route exact path="/leaderboard" render={(props) => (
<>
<WebsiteLeaderBoard />
</>
)} />
</Switch>
</LeaderBoardApp>
);
const container = [ dashboardContainer , leaderBoardContainer ];
const Routes = () => {
return (
<BrowserRouter baseName="/">
<Switch>
{container.map((pages,id) => (
pages(id)
))}
</Switch>
</BrowserRouter>
);
};
export default Routes;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This is my DashboardApp.js
import React, { Component } from "react";
import { WebsiteHeader , WebsiteFooter} from "../components";
import PropTypes from "prop-types";
import classes from "./app.scss";
class DashBoardApp extends Component {
render() {
return (
<div className={classes.app}>
<WebsiteHeader/>
<>
{this.props.children}
</>
<WebsiteFooter />
</div>
);
}
}
export default DashBoardApp;
DashBoardApp.propTypes = {
children : PropTypes.element,
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This is my LeaderBoardApp.js
import React, { Component } from "react";
import { WebsiteHeader , WebsiteFooter} from "../components";
import PropTypes from "prop-types";
import classes from "./app.scss";
class LeaderBoardApp extends Component {
render() {
return (
<div className={classes.app}>
<WebsiteHeader/>
<>
{this.props.children}
</>
<WebsiteFooter />
</div>
);
}
}
export default LeaderBoardApp;
LeaderBoardApp.propTypes = {
children : PropTypes.element,
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I have set my Link:
export default function HeaderLink (props) {
const { wrapperClass, dashboardClass } = props;
return (
<ul className={wrapperClass}>
<li className={[dashboardClass,classes.active].join(" ") }><Link to ="/dashboard">Dashboard</Link></li>
<li className={dashboardClass}> <Link to ="/leaderboard">Leaderboard</Link></li>
</ul>
);
}
According to this the link should work , but when i try to click the leaderboard link , it doesn't render the Websiteleaderboard component. But it always render the First route when i click the dashboard link as it's the first after switch statement.
I searched online , and thought about it a lot , but couldn't found any solution. I don't know what's the problem here.
Here's the picture of rendered routes:
First pic
Second pic
You have multiple <Switch> elements in what you're trying to do. From the docs:
When a <Switch> is rendered, it will only render the first child that matches the current location.
Docs here
Removing all the switches except the wrapper should work. However, might I suggest a cleaner approach:
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import {
WebsiteFilter,
WebsiteLeaderBoard,
WebsiteGraph,
WebsiteQues
}
from "./components";
import { DashboardApp ,LeaderBoardApp} from "./containers";
const dashboardContainer = () => (
<DashboardApp>
<WebsiteFilter />
<WebsiteGraph />
<WebsiteQues />
</DashboardApp>
);
const leaderBoardContainer = () => (
<LeaderBoardApp>
<WebsiteLeaderBoard />
</LeaderBoardApp>
);
const Routes = () => {
return (
<BrowserRouter baseName="/">
<Switch>
<Route path="/dashboard" component={dashboardContainer} />
<Route path="/leaderboard" component={leaderBoardContainer} />
</Switch>
</BrowserRouter>
);
};
export default Routes;
By doing this, your entire configuration is contained in the Routes. You could even move the components out of that file, and have your Routes contained to only the actual routes, while importing the components you want to use.

Resources