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>;
}
Related
I am new to React router here and I am trying to make clicking on a recipe in my 'BrowseRecipes' page redirect to a page dedicated to that recipe. However, when I click on the recipe, the URL shows the correct URL /browse/${recipeID}, but the page I assign to this route does not render. Only the /browse page with a list of all the recipes renders. Does anyone know why?
Here is my APP.js
import AddNewRecipe from './components/AddNewRecipe'
import BrowseRecipes from './components/BrowseRecipes'
import { currentState } from './components/redux';
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Switch, Route, Routes, Link, useParams} from "react-router-dom";
import AuthReqPage from "./components/AuthReqPage"
import Navbar from "./components/Navbar"
import RecipePage from "./components/BrowseRecipes/RecipePage"
import PageNotFound from "./components/PageNotFound"
function App(props) {
return (
<Router>
<div className="App">
<Navbar />
<Routes>
<Route path='/add' element={<AddNewRecipe />} />
<Route path='/' element={<BrowseRecipes />} />
<Route path='/browse' element={<BrowseRecipes />}>
<Route path=':recipeID' element={<RecipePage />}/>
</Route>
<Route path='/authrequired' element={<AuthReqPage />} />
<Route path='/*' element={<PageNotFound />} />
</Routes>
</div>
</Router>
);
}
export default App;
Here is my BrowseRecipe component/page:
export function BrowseRecipes (props){
console.log('browseRecipe running')
let navigate = useNavigate()
let params=useParams()
console.log(params.recipeID)
if(props.recipeStore.length>0)
{
var displayRecipes = props.recipeStore.map(
elem=>
{
return (<li key={elem.recipeID} className='recipeDisplayBox' onClick={()=>navigate(`/browse/${elem.recipeID}`)}>
{elem.title},
Prep: {elem.prepTime.numeral} {elem.prepTime.unit}
</li>)
}
)
}
return(
<div>
<h1>Browse Recipes</h1>
<h2>Your recipes:</h2>
<ul>
{displayRecipes}
</ul>
</div>
)
}
const mapStateToProps=(state)=>{
return {recipeStore: state.recipe}}
export default connect(mapStateToProps)(RequireAuth(BrowseRecipes))
And here is the individual recipe page that failed to render:
export function RecipePage (props){
console.log('RecipePage running')
let params=useParams()
return(
<div>
<h1>{params.recipeID}</h1>
</div>
)
}
const mapStateToProps=(state)=>{
return {recipeStore: state.recipe}}
export default connect(mapStateToProps)(RequireAuth(RecipePage))
"RequireAuth" here is a higher-order component that redirects the page to 'Please Sign In' page if the user is not signed in.
Did I misunderstand something about the use of UseParams? Please help me shed some light! Thank you very much
You've rendered the RecipePage component on a nested route from the "/browse" route rendering the BrowseRecipes component.
<Route path='/browse' element={<BrowseRecipes />}>
<Route path=':recipeID' element={<RecipePage />}/>
</Route>
In this configuration the BrowseRecipes is required to render an Outlet component for the nested routes to be rendered into.
Example:
import { Outlet, useNavigate, useParams } from 'react-router-dom';
export function BrowseRecipes (props) {
const navigate = useNavigate();
const params = useParams();
let displayRecipes;
if (props.recipeStore.length) {
displayRecipes = props.recipeStore.map(elem => {
return (
<li
key={elem.recipeID}
className='recipeDisplayBox'
onClick={() => navigate(`/browse/${elem.recipeID}`)}
>
{elem.title},
Prep: {elem.prepTime.numeral} {elem.prepTime.unit}
</li>
);
});
}
return (
<div>
<h1>Browse Recipes</h1>
<h2>Your recipes:</h2>
<ul>
{displayRecipes}
</ul>
<Outlet /> // <-- nested routes render here
</div>
);
}
If you don't want to render both BrowseRecipes and RecipePage at the same time, then create a nested index route specifically for BrowseRecipes.
Example:
<Route path='/browse'>
<Route index element={<BrowseRecipes />} /> // <-- "/browse"
<Route path=':recipeID' element={<RecipePage />} /> // <-- "/browse/:recipeID"
</Route>
For more information, see:
Index Routes
Layout Routes
I'm new to react and is trying out the React.lazy and Suspense imports, and I just have to say, I love them!!! My website went from 45% in performance up to 50-60% and that is without optimizing images! Google search results, here I come!
However, I have a problem, I don't know how to lazy load a component which is rendered in my custom ProtectedRoute and react-router-dom v5.
The lazy loading works and takes effect when I use the React-router-doms native Route, but when I want to load a protected component via one in my custom protected routes, nothing happens, no error message in console or on the website, just a white screen. I suspect there's some problem with the import and code being put in the wrong place.
APP
import React, { Suspense } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import ProtectedRoute from "./pages/middleware/ProtectedRoute";
const Login = React.lazy(() => import("./pages/Login"));
const WebsiteCRUDs = React.lazy(() => import("./pages/WebsiteCRUDs"));
function App() {
return (
<div className="App">
<Router>
<Switch>
{/* This one works */}
<Suspense fallback={<div>Loading</div>}>
<Route exact path="/admin" component={Login} />
</Suspense>
{/* This one does NOT work */}
<Suspense fallback={<div>Loading</div>}>
<ProtectedRoute exact path="/admin/crud" component={WebsiteCRUDs} />
</Suspense>
</Switch>
</Router>
</div>
);
}
export default App;
ProtectedRoute:
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { useEffect, useState } from "react";
const ProtectedRoute = ({ component: Component, ...rest }) => {
const [isAuth, setIsAuth] = useState(false);
const [isLoading, setIsLoading] = useState(true);
// Logic validation goes here with redirect if user is not auth.
return (
<Route
{...rest}
render={(props) =>
isLoading ? (
<h1>Checking Validation</h1>
) : isAuth ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: "/admin", state: { from: props.location } }}
/>
)
}
/>
);
};
export default ProtectedRoute;
Please try like this
import React, { Suspense } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import ProtectedRoute from "./pages/middleware/ProtectedRoute";
const Login = React.lazy(() => import("./pages/Login"));
const WebsiteCRUDs = React.lazy(() => import("./pages/WebsiteCRUDs"));
function App() {
return (
<div className="App">
<Router>
<Switch>
<Suspense fallback={<div>Loading</div>}>
<Route exact path="/admin" component={Login} />
<ProtectedRoute exact path="/admin/crud" component={WebsiteCRUDs} />
</Suspense>
</Switch>
</Router>
</div>
);
}
export default App;
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 :)
I have a parent component which is rendering some stuff and a child component which is getting some props from parent component. And when user click button in Parent then it should redirect to completely new Page of child componen.
Parent Component
import React from "react";`enter code here`
import "./styles.css";
import { BrowserRouter as Router, Link, Route, Switch } from 'react-router-dom'
import ChildComponent from './ChildComponent'
export default function App() {
const someData = {
name : "Joh Doe"
}
return (
<div className="App">
<h1>This is parent Component</h1>
<Router>
<Link to='/secondpage'>Click me for Second Page</Link>
<Route
path='/secondpage'
render={(props) => (
<ChildComponent {...someData} isAuthed={true} />
)}
/>
</Router>
</div>
);
}
ChildComponent.js
import React from "react";
export default function ChildComponent(props) {
console.log("Data", props);
return <div>This is Second Page. It should open in new page. Also it should render incoming props</div>;
}
Work Demo
try doing this:
import React from 'react';
import {BrowserRouter as Router,Route, Redirect,Switch} from 'react-router-dom';
import Home from './App.js';
import Tutorials from './tutorials.js';
function Routes() {
return (
<Router>
<div>
<Switch>
<Route path="/" component = {Home}>
<Redirect from = "/blog/" to="/tutorials/" />
<Route path = "/tutorials/" component = {About} />
</Switch>
</div>
</Router>
)
}
i am change code .
parent component
import React from "react";
import { Link } from "react-router-dom";
export default function ParentComponent(props) {
console.log("Data", props);
return (
<div className="App">
<h1>This is parent Component</h1>
<Link to="/secondpage">Click me for Second Page</Link>
</div>
);
}
child component
import React from "react";
export default function ChildComponent(props) {
console.log("Data", props);
return (
<div>
This is Second Page. It should open in new page. Also it should render
incoming props
</div>
);
}
app component
import React from "react";
import "./styles.css";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import ChildComponent from "./ChildComponent";
import ParentComponent from "./parentComponent";
export default function App() {
const someData = {
name: "Joh Doe"
};
return (
<div className="App">
<Router>
<Switch>
<Route exact path="/secondpage">
<ChildComponent {...someData} isAuthed={true} />
</Route>
<Route exact path="/" component={ParentComponent} />
</Switch>
</Router>
</div>
);
}
Work Demo
After several attempts, I have managed to implement basic nested-routing with React-router-dom.
Here's the simple project structure:
Here are the relevant files:
App.js
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import ParentComponent from "./Components/nestedComponents/ParentComponent";
import NavBar from "./Components/Shared/NavBar";
function App() {
return (
<div className="App">
<BrowserRouter>
<NavBar />
<Switch>
<Route path="/home" name="Home" component={ParentComponent} />
</Switch>
</BrowserRouter>
</div>
);
}
export default App;
NavBar.js
import React from "react";
import { Link } from "react-router-dom";
export default function NavBar() {
return (
<div>
<Link to={`home/nestedComponentOne`}> Nested Component One </Link>
<Link to={`home/nestedComponentTwo`}> Nested Component Two </Link>
</div>
);
}
ParentComponent.js
import React from "react";
import nestedComponentOne from "./nestedComponentOne";
import nestedComponentTwo from "./nestedComponentTwo";
import { Switch, Route } from "react-router-dom";
export default function ParentComponent() {
return (
<div>
<Switch>
<Route path="/home/nestedComponentOne" component={nestedComponentOne} />
<Route path="/home/nestedComponentTwo" component={nestedComponentTwo} />
</Switch>
</div>
);
}
nestedComponentOne.js
import React from "react";
export default function nestedComponentOne() {
return <div>NESTED COMPONENT 1</div>;
}
nestedComponentTwo.js
import React from "react";
export default function nestedComponentTwo() {
return <div>NESTED COMPONENT 2</div>;
}
So here's the Result:
If I click on nestedComponentOne:
If I click on nestedComponentTwo:
The problem is when I click again on nestedComponentOne (or Two) after the I have clicked it the first time, the route gets added to the url string instead of replacing it:
Some update need for your code.
Working Demo
NavBar.js
Here you forget to add slash / at front to link from root.
<Link to={`/home/nestedComponentOne`}> Nested Component One </Link>
<Link to={`/home/nestedComponentTwo`}> Nested Component Two </Link>
ParentComponent.js
As we removed the Switch from this component, so we need to get the matching information from parent router and pass the path to navigate the corresponding your nested component
export default function ParentComponent({ match }) {
return (
<div>
<Route path={`${match.path}/nestedComponentOne`} component={nestedComponentOne} />
<Route path={`${match.path}/nestedComponentTwo`} component={nestedComponentTwo} />
</div>
);
}
Why don't you try putting all the route in one file. Something like this:
<Route exact path="/home" name="Home" component={ParentComponent} />
<Route path="/home/nestedComponentOne" component={nestedComponentOne} />
<Route path="/home/nestedComponentTwo" component={nestedComponentTwo} />