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.
Related
Let's say we have a component Home.jsx
const Home = () => {
return (
<div>
<h1>Home</h1>
</div>
);
};
export default Home;
and our router goes as
const Router = () => (
<>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</>
);
That's works fine! But what if we want to import the element as below in the Route component
views.js
export { default as Home } from "./home";
and modify our routes as follows
import * as views from "../views";
<Routes>
<Route path="/" element={views.Home} />
</Routes>
this piece of code makes following error:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
What's the way out?
Well, I come up with a workaround, and that is use variable as method.
Here it is:
<Route path="/" element={views.Home()} />
My App.js:
<Router>
<Header/>
<Switch>
<Route exact path="/" component={HomeScreen} />
<Route exact path="/screenOne" component={OneScreen} />
<Route exact path="/screenTwo" component={TwoScreen} />
</Switch>
</Router>
The <Header /> has three links to the respective components viz. HomeScreen, OneScreen, TwoScreen.
I want my <TwoScreen /> to be exactly like this baseComponent(i.e App.js) where I have some links and when I click those links, the components corresponding to the link/path gets rendered.
What is the best way to approach this?
there is an example on the react-router site that meets your need, under nesting, you can check it out https://reactrouter.com/web/example/nesting (for just web)
But for me, I simply declare them all in one place/component, especially on react-router-native, if you're doing react-native.
below is the example from their website for web
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams,
useRouteMatch
} from "react-router-dom";
// Since routes are regular React components, they
// may be rendered anywhere in the app, including in
// child elements.
//
// This helps when it's time to code-split your app
// into multiple bundles because code-splitting a
// React Router app is the same as code-splitting
// any other React app.
export default function NestingExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/topics">
<Topics />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function Topics() {
// The `path` lets us build <Route> paths that are
// relative to the parent route, while the `url` lets
// us build relative links.
let { path, url } = useRouteMatch();
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${url}/components`}>Components</Link>
</li>
<li>
<Link to={`${url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
<Switch>
<Route exact path={path}>
<h3>Please select a topic.</h3>
</Route>
<Route path={`${path}/:topicId`}>
<Topic />
</Route>
</Switch>
</div>
);
}
function Topic() {
// The <Route> that rendered this component has a
// path of `/topics/:topicId`. The `:topicId` portion
// of the URL indicates a placeholder that we can
// get from `useParams()`.
let { topicId } = useParams();
return (
<div>
<h3>{topicId}</h3>
</div>
);
}
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.
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
Based on my understanding, once you have implemented React Router, your website should not display any components unless the Route path matches the URL. However, my code is not behaving that way. I have things implemented, but the Invoices component is still wanting to render, even though the URL is '/'. How do I fix this problem?
UPDATED to prevent components from rendering and to show more code for LeftMenu
I'm still confused on whether or not I even need to use React Router for my situation.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './pages/App';
import { BrowserRouter as Router } from 'react-router-dom';
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root'),
);
App.js
return (
<>
<GlobalStyle />
<TopMenu
setDisplay={setDisplay}
display={display}
openNav={openNav}
closeNav={closeNav}
/>
<Wrapper>
<LeftMenu changeSection={changeSection} setSection={setSection} />
<MainStyle>
<BreadCrumbs
changeSection={changeSection}
number={number}
month={month}
breadCrumbs={breadCrumbs}
setSection={setSection}
/>
<Portal>
{section === 'home' && <Home />}
{section === 'invoice' && (
<Invoice>
<Invoices
invoices={invoices}
updateNumber={updateNumber}
updateMonth={updateMonth}
number={number}
month={month}
download={download}
/>
</Invoice>
)}
{section === 'act' && <ACT />}
{section === 'external' && <External />}
</Portal>
</MainStyle>
</Wrapper>
</>
);
LeftMenu.js
const LeftMenu = ({ changeSection }) => {
return (
<NavWindow>
<ul>
<li>
<Link to='/Employer Invoices'>Employer Invoices</Link>
</li>
<li>
<Link to='/ACT'>Enroll Employee</Link>
</li>
<li>
<Link to='/ACT'>Terminate Employee</Link>
</li>
<li>
<Link to='/ACT'>Employee Changes</Link>
</li>
</ul>
<Switch>
<Route exact path='/' component={Home} />
<Route path='/Employer Invoices' component={Invoices} />
<Route path='/Employer Invoices/:invoiceId' component={Invoices} />
<Route path='/Enroll Employee' component={ACT} />
<Route path='/Terminate Employee' component={ACT} />
<Route path='/Employee Changes' component={ACT} />
</Switch>
</NavWindow>
);
};
First, there is no link between your LeftMenu component and you App component here. Your leftMenu will never be rendered, and your App will always render all it contains(Home, Invoice, ACT, External...).
Second, the following assertion:
once you have implemented React Router, your website should not display any components unless the Route path matches the URL
is false.
In your context, if you change your index.js code from that:
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root'),
);
to that:
ReactDOM.render(
<Router>
<LeftMenu />
</Router>,
document.getElementById('root'),
);
I guess that it will get close to what you want. It will always render your NavWindow. Links inside will always be rendered, and above the ul tag will be rendered the component whose route match current path.
Here is a fiddle with you code and some modification to make it work, I let you discover the differences. Don't hesitate if you want more explanations.
https://jsfiddle.net/6atxpr15/
You need to wrap App in a Route component (https://reacttraining.com/react-router/web/api/Route) :
import { BrowserRouter as Router, Route } from 'react-router-dom'
ReactDOM.render(
<Router>
<Route path='/' component={App} />
</Router>
document.getElementById('root'),
);