Stop the re-rendering of Sidebar in Page using react-router-dom - reactjs

Any help would be appreciated, So i have a page with Header, Sidebar, Footer and Main, where Sidebar has static links, which when clicked display the components. The issue here is on clicking the links, sidebar,header and footer are re-rendering which is not required. I have tried shouldComponentUpdate in sidebar but it won't work.
Versions used by the project:
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
I'll be here till this issue is resolved so feel free to ask any question
here is myApp.js (the root file)
function App() {
return (
<Provider store={Store}>
<Router history={history}>
<AppRoutes />
</Router>
</Provider>
);
}
now the AppRoutes component has following method
const RouteList = [
{
path: "/",
component: Dashboard,
guest: false,
exact: true
},
{
path: "/security_info",
component: SecurityInfoPage,
guest: false,
exact: true
},
]
class AppRoutes extends Component {
componentDidMount() {
...here we fetch the login info from store
isAuthenticated = true
}
render() {
...if it has access token, it
return (
<Switch>
{RouteList.map((route, i) => (
route.guest === false
? <PrivateRoute isAuthenticated={isAuthenticated} key={i} {...route} />
: <AppRoute key={i} {...route} />
)
)}
</Switch>
);
}
}
as is_authenticated is true, it goes to private route inside AppRoute.js file
const PrivateRoute = ({isAuthenticated, component: Component, ...rest }) => (
<Route
{...rest}
render={(props) => (
isAuthenticated === true
? <DashboardLayout>
<Component {...props}/>
</DashboardLayout>
: <Redirect to='/login' />
)}
/>
)
it goes to dashboardlayout where it has multiple components
<div className={'wrapper'}>
<Navigation />
<div className="page-content">
<Sidebar />
<div className="content-wrapper">
{children}
<MessageSideBar />
<Footer />
</div>
</div>
</div>
Now as i click on a different link, it goes to dashboard layout where its prop children gets changed rendering the entire dashboard including header, footer, sidebar.
Edit 1:
Here is the Sidebar file
class Sidebar extends Component {
componentDidMount = () => {
it is requesting data from 3 api's
this.props.dispatch(sidebarAction.sidebarDetail())
this.props.dispatch(settingAction.getCreditAmount())
this.props.dispatch(messageAction.getUnReadMessageCount())
}
render(){
return(
<ul>
<li>
<NavLink
exact={true}
to="/" >
<span>Dashboard</span>
</NavLink>
</li>
<li>
<NavLink to="/security_info">
<span>Security Information</span>
</NavLink>
</li>
</ul>
)}
Though there are like 10+ NavLinks but i have included only 2 and also removed irrelevant classnames

Incorrect way
Your routes are structured like the following snippet, which will cause rerendering the Dashboard component every time you switch the route.
<Route path="/subComponent" component={SubComponent}/>
const SubComponent = () => {
return (
<Dashboard>
// Here is your content place
</Dashboard>
)
}
Correct way
However, the correct solution would be to put the routes directly inside the Dashboard component where you would like to render your components wrapped by the layout (Dashboard) like this:
<Dashboard>
<DashboardMenu>
<SideNav /> // e.g. containing links with history.push()
</DashboardMenu>
// here is the place where the components wrapped by the layout should be rendered, so place the routes here
<Route path={...} component={() => <ComponentInsideDashboard />}
</Dashboard>
You should render the actual content (the dynamic one, not the static Dashboard) already inside the Dashboard. Since every route returns the dynamic components wrapped inside Dashboard, the Dashboard will get rendered multiple times.
To put it even more simply: Since you want the Dashboard to get rendered only once, there should just be only one place where you are using it.
Correct way with different layouts
If you also want to render content without a layout (Dashboard) or multiple different ones, you can simply make use of nesting routes.
export const BaseApplicationRoutes = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/dashboard1" component={<Dashboard1 />}/>
<Route path="/dashboard2" component={<Dashboard2 />}/>
<Route path="/noDashboard" component={<NoDashboard />}/>
</Switch>
</BrowserRouter>
)
}
<Dashboard1> // Dashboard2 could also look like this
<Dashboard1Menu>
<SideNav /> // e.g. containing links with history.push()
</Dashboard1Menu>
// Routes containing dynamic content
<Route path={...} component={() => <ComponentWithDashboard1 />}
</Dashboard1>
<NoDashboard>
// any content or even more routes rendering content without a layout
</NoDashboard>

Related

How to hide Navbar in Login Component with ReactJS?

Recently I started learning React and my problem here is that I cannot hide Navbar when Im in Login page/component. I have a Router in index.js like this:
const routing = (
<div>
<NavBar />
<Router>
<div>
<Switch>
<Route exact path="/" component={App} />
<Route path="/users/:id" component={Users} />
<Route path="/users" component={Users} />
<Route path="/contact" component={Contact} />
<Route path="/login" component={LoginPage} hideNavBar={true} />
<Route component={Notfound} />
</Switch>
</div>
</Router>
</div>
)
ReactDOM.render(routing, document.getElementById('root'));
From the little search that I made most approaches were like inserting <NavBar /> in every component and use a flag to hide it when im in Login. Is there any other way like modifying the above code simple and fast?
Not really you could have restrict access to all other routes like below I suppose
const PrivateRoute = ({component: Component, ...rest }) =>
<Route
{...rest}
render={props =>
authenticated ? <Container ><Component {...props} /> <Container /> : <Redirect to="/login" />
}
/>
};
Where you container has a header , maybe footer and takes a child prop (your component)
There are better approaches for hiding the NavBaron authenticated routes, but if you want to hide it when it's on foo route, you could check the path name and decide to render it or not.
import { useLocation } from "react-router-dom";
const NavBar = () => {
const location = useLocation()
return location.pathname == '/login' ? <YourNavBarComponents /> : null
}
The best way to hide parts of the UI building with React is to not include the markup. In order to do NOT display parts relying on the authentication you should have a flag if the authentication has done and is successful.
For example you can build a component that share some context in order to check if the authentication flag is true and render the parts that must be available only to users who have logged in.
You can follow this example to see the details how to build that component.
Basically you have a component that wraps another component and based on some rules render its children or call a render prop:
function Private({ shouldRender = false, children }) {
return shouldRender
? children
: null;
}
function App() {
return (
<div>
<h2>Application</h2>
<Private><div>This part is hidden</div></Private>
<Private shouldRender={true}><div>This part is <strong>available</strong></div></Private>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#root'));
<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>
<div id="root"></div>

How to redirect to log in page after click logout button on navbar header in React?

I'm new to React. I have react router config in App.js like this:
<BrowserRouter>
<div className="App">
<Header />
<Switch>
<Route exact path="/" component={Home}>
</Route>
<Route exact path="/management" component={Management}>
</Route>
<Route exact path="/sign-up" component={SignUpForm}>
</Route>
<Route exact path="/sign-in" component={SignInForm}>
</Route>
<Route component={Error}>
</Route>
</Switch>
</div>
</BrowserRouter >
I want header to show in every page, there's a button of logout at header, I want to redirect to /sign-in page after I click it. In my header component it's like this:
class Header extends Component {
constructor(props) {
super(props);
this.state = {
redirect: false
}
}
logout = () => {
sessionStorage.setItem("userToken", '');
sessionStorage.clear();
this.setState({ redirect: true });
}
render() {
if (this.state.redirect) {
return (
<Redirect to={'/sign-in'} />
)
}
return (
<div>
<Navbar collapseOnSelect expand="md" bg="dark" variant="dark" fixed="top" >
......
<NavLink to="/management" className="header-link"><FontAwesomeIcon icon="cog" size="lg" /></NavLink>
<button type='button' onClick={this.logout}>Log Out</button>
</Nav>
</Navbar.Collapse>
</Navbar>
</div>
);
}
}
export default Header;
There will be errors "Warning: You tried to redirect to the same route you're currently on: "/sign-in", and the nav bar will disappear only the body of sign-in shows. May I know what is the correct way to do this? I also tried this.props.history.push('/sign-in') but there's no props.history, probably because header is not in route? Should i use with Router? Or should I actually just make every page import header instead put it in app.js? or what is actually the right way to do this? Thank you so much for your help!
You can implement login/logout with route using HOC that checks the session item with every route change. If the session has userToken then it will redirect to given component otherwise will redirect to login component.
import React from "react"
import {Redirect} from "react-router-dom"
export const PrivateRoute = ({component: Component, ...rest}) => (
<Route {...rest} render={(props) => (
sessionStorage.getItem('userToken') ? <Component {...props} /> : <Redirect to="/sign-in"/>
)} />
)
import <PrivateRoute> and use it as the authorized path. And keep all the other path as normal routes in which you don't want authorization.
<BrowserRouter>
<div className="App">
<Header />
<Switch>
<PrivateRoute path="/" component={Home} />
<PrivateRoute path="/management" component={Management} />
<Route path="/sign-up" component={SignUpForm} />
<Route path="/sign-in" component={SignInForm} />
<Route component={Error} />
</Switch>
</div>
</BrowserRouter >
So while you do log out, the session item will be removed and automatically redirect to sign-in page.
class Header extends Component {
....
logout = () => {
sessionStorage.removeItem("userToken");
sessionStorage.clear();
}
render() {
return (
<div>
...
<button type='button' onClick={this.logout}>Log Out</button>
</div>
)
}
}
export default Header;
Components tied up to Routes gets access to history object as prop so you can mutate it as you need, such as logging out. Since your Header component doesn't have access to the history object, you will have to use a lower level router to give it access to the history object:
import { Router } from "react-router"
import { createBrowserHistory } from "history"
const history = createBrowserHistory()
<Router history={history}>
<div className="App">
<Header history={history} />
<Switch>
<Route exact path="/" component={Home}>
</Route>
<Route exact path="/management" component={Management}>
</Route>
<Route exact path="/sign-up" component={SignUpForm}>
</Route>
<Route exact path="/sign-in" component={SignInForm}>
</Route>
<Route component={Error}>
</Route>
</Switch>
</div>
</Router>
now inside you Header component you can call history.push('/login')
see reference: https://reacttraining.com/react-router/web/api/Router/history-object
There are two approaches you're mentioning here. You can use the higher order component 'withRouter' that gives components access to the history object. By using the history object that would get passed to your component as a prop, you can push to the route you want.
Personally, I like setting up my signout links to render a component that houses the log-out logic and renders a redirect to log-in once it's complete. That way users can go directly to the sign-out link if they want, and you can link to it from anywhere in your app as needed, without having to duplicate the logic.
In your browser router, you can add a path for "/logout" that renders a component like this (based on your logic):
import React, { Component } from 'react';
import { Redirect } from 'react-router';
export default class LogOut extends Component {
state = {
redirect: false,
};
componentDidMount() {
sessionStorage.setItem("userToken", '');
sessionStorage.clear();
this.setState({ redirect: true });
}
render() {
return this.state.redirect ?
<Redirect to={'/sign-in'} /> :
null;
}
}
Normally I would make an ajax request to clear a session and then setState once that's complete, but yours is all server-side.

React: Hide a Component on a specific Route

New to React:
I have a <Header /> Component that I want to hide only when the user visit a specific page.
The way I designed my app so far is that the <Header /> Component is not re-rendered when navigating, only the page content is, so it gives a really smooth experience.
I tried to re-render the header for every route, that would make it easy to hide, but I get that ugly re-rendering glitch each time I navigate.
So basically, is there a way to re-render a component only when going in and out of a specific route ?
If not, what would be the best practice to achieve this goal ?
App.js:
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<Frame>
<Canvas />
<Header />
<Main />
<NavBar />
</Frame>
</div>
</BrowserRouter>
);
}
}
Main.js:
const Main = () => (
<Switch>
<Route exact activeClassName="active" path="/" component={Home} />
<Route exact activeClassName="active" path="/art" component={Art} />
<Route exact activeClassName="active" path="/about" component={About} />
<Route exact activeClassName="active" path="/contact" component={Contact} />
</Switch>
);
I'm new to React too, but came across this problem. A react-router based alternative to the accepted answer would be to use withRouter, which wraps the component you want to hide and provides it with location prop (amongst others).
import { withRouter } from 'react-router-dom';
const ComponentToHide = (props) => {
const { location } = props;
if (location.pathname.match(/routeOnWhichToHideIt/)){
return null;
}
return (
<ComponentToHideContent/>
)
}
const ComponentThatHides = withRouter(ComponentToHide);
Note though this caveat from the docs:
withRouter does not subscribe to location changes like React Redux’s
connect does for state changes. Instead, re-renders after location
changes propagate out from the component. This means that
withRouter does not re-render on route transitions unless its parent
component re-renders.
This caveat not withstanding, this approach seems to work for me for a very similar use case to the OP's.
Since React Router 5.1 there is the hook useLocation, which lets you easily access the current location.
import { useLocation } from 'react-router-dom'
function HeaderView() {
let location = useLocation();
console.log(location.pathname);
return <span>Path : {location.pathname}</span>
}
You could add it to all routes (by declaring a non exact path) and hide it in your specific path:
<Route path='/' component={Header} /> // note, no exact={true}
then in Header render method:
render() {
const {match: {url}} = this.props;
if(url.startWith('/your-no-header-path') {
return null;
} else {
// your existing render login
}
}
You can rely on state to do the re-rendering.
If you navigate from route shouldHide then this.setState({ hide: true })
You can wrap your <Header> in the render with a conditional:
{
!this.state.hide &&
<Header>
}
Or you can use a function:
_header = () => {
const { hide } = this.state
if (hide) return null
return (
<Header />
)
}
And in the render method:
{this._header()}
I haven't tried react-router, but something like this might work:
class App extends Component {
constructor(props) {
super(props)
this.state = {
hide: false
}
}
toggleHeader = () => {
const { hide } = this.state
this.setState({ hide: !hide })
}
render() {
const Main = () => (
<Switch>
<Route exact activeClassName="active" path="/" component={Home} />
<Route
exact
activeClassName="active"
path="/art"
render={(props) => <Art toggleHeader={this.toggleHeader} />}
/>
<Route exact activeClassName="active" path="/about" component={About} />
<Route exact activeClassName="active" path="/contact" component={Contact} />
</Switch>
);
return (
<BrowserRouter>
<div className="App">
<Frame>
<Canvas />
<Header />
<Main />
<NavBar />
</Frame>
</div>
</BrowserRouter>
);
}
}
And you need to manually call the function inside Art:
this.props.hideHeader()
{location.pathname !== '/page-you-dont-want' && <YourComponent />}
This will check the path name if it is NOT page that you DO NOT want the component to appear, it will NOT display it, otherwise is WILL display it.

Link page bounded click handlers to Footer component

Right now I have a Footer component with three different buttons. The onClick of two of those buttons depends on the page, and are defined in those pages. Currently I render the Footer component in the Page components separately, where the onClick functions are defined and handed as a prop, and is working great.
However I added the CSS element max-width to the pages, and is affecting the Footer too, which I don't want.
To solve this I need to render the Footer outside of the Page components, and in the Router component, just like the Header. But the issue is that I don't know how to link the click handlers of those two Footer buttons, which are defined in the Page components.
What would be the best way to solve this? Push the onClick functions in my Redux store?
Currently it's working like this:
Router.js
<div id="router-container">
<Header />
<Switch>
<Route path="/dashboard" component={DashboardPage} />
<Route path="/settings" component={SettingsPage} />
</Switch>
</div>
DashboardPage.js
const DashboardPage = () => (
<div id="dashboard-page">
...content
<Footer onRightButtonClick={myDashboardPageClickHandler} />
</div>
);
SettingsPage.js
const SettingsPage = () => (
<div id="settings-page">
...content
<Footer onRightButtonClick={mySettingsPageClickHandler} />
</div>
);
But how can I get it to work like this:
Router.js (new)
<div id="router-container">
<Header />
<Switch>
<Route path="/dashboard" component={DashboardPage} />
<Route path="/settings" component={SettingsPage} />
</Switch>
<Footer />
</div>
DashboardPage.js / SettingsPage.js
const DashboardPage = () => {
// set the onClick of the footer somehow to myDashboardPageClickHandler()
return (
<div id="dashboard-page">
...content
</div>
);
};
I would recommend using the withRouter function of React Router to transform the Footer, like so:
const FooterWithRouter = withRouter(Footer);
then, inside the Footer component, define functions like so:
handleRightButtonClick() {
const pathname = { this.props.location };
if ( pathname === '/dashboard' ) {
return myDashboardPageClickHandler;
} else if ( pathname === '/settings' ) {
return mySettingsPageClickHandler;
}
}
and assign the onClick of the button to the return value of this function, like so:
<button class="right-arrow" onClick={handleRightButtonClick()}></button>
See if this works.

React set state/props on route using react router

I am somewhat new to React. so please bear with me. I have the following base structure:
<App>
this.props.children
</App>
...and in children, one component is a header that has what I want to be an optional search component:
<Header>
...some other children...
<Search /> <- STUCK HERE SHOULD BE OPTIONAL!!!
</Header>
...children from other components...
What I am trying to do is say when I go to route A, the search component should not be included (or at least not shown), but when I go to route B, it should. I have scoured for days and so far been unable to find a solution that meets this need. If it matter, I am using ES6/7 (babel via webpack).
I can set state in the APP and toggle it literally to adjust the passed down props on the Search and show or not show it, but cannot figure out how to do that dynamically based on the route.
The core issue is how to tell App (and indirectly Header) to show the search component on inside the Header on some routes, but not on others. I 'think' maybe I need some sort of abstraction/wrapper component in the middle, but am not really sure. Any though or ideas are welcome.
TIA!
First setup your routes.
<Router path="/" component={App}>
<Route path="foo" component={Header} showSearch={true} />
<Route path="bar" component={Header} showSearch={false} />
</Router>
The route will be passed down as a property, then you can access the showSearch property, which determines whether the search component is rendered.
// Header
render() {
const { showSearch } = this.props.route;
return (
<div className='header'>
// ... other components
{ showSearch ? <Search /> : null }
</div>
);
}
Maybe you don't want your header to be the top level component though. In that case define an intermediary wrapper component that forwards the route props down to the header.
<Router path="/" component={App}>
<Route path="foo" component={Shell} showSearch={true} />
<Route path="bar" component={Shell} showSearch={false} />
</Router>
// Shell
render() {
const { route } = this.props;
return (
<div className='shell'>
<Header {...route} />
</div>
);
}
Alternatively, you could do a quick and dirty check from inside your Header component.
// Header
render() {
const { hash } = window.location,
showSearch = /\/foo/.test(hash);
return (
<div className='header'>
// ... other components
{ showSearch ? <Search /> : null }
</div>
);
}
If you want to use functional components, React Router has specifically created an API for this called render for this purpose.
Example:
<Route
path='/search'
render={(props) => (
<Header {...props} showSearch={true} />
)}
/>
Then just simply use the props as normal in your component:
interface HeaderProps {
showSearch: boolean
}
export const Header: React.FC<HeaderProps> = ({ showSearch }) => {
return (
<React.Fragment>
{ showSearch ? <Search /> : null }
</React.Fragment>
)
}
See the excellent article written by Tyler McGinnis regarding this implementation:
https://ui.dev/react-router-v4-pass-props-to-components/

Resources