I am using ReactGo boiler-plate to develop an application.
Using react-router for navigating through the site and this is the code,
import { Route, IndexRoute } from 'react-router';
<Route path="/" component={App}>
<IndexRoute component={HomePage} />
<Route path="/login" component={SignIn} pageType="checkout" />
</Route>
and this is the App component,
class App extends Component {
render() {
const rounteProps = this.props.children.props.route;
const pageType = rounteProps.pageType;
return (<div className="app">
{pageType === 'checkout' ? (<CheckoutHeader />) : (<Header />)}
<div className="grid-wrapper site-page content--centered">
<main className="grid grid--space-y site-main">
{this.props.children}
</main>
<Footer />
<Loader />
</div>
</div>);
}
}
I am using pageType for conditional rendering of Header component as there are 2 different headers I have. This works fine with all the routes which do not have any pageType / common pageType.
However when I go to the page /login which has pageType=checkout. It reloads the entire page (and as per my observation it loads the component twice). Don't know why is this happening? Any help would be appreciated. Please let me know if you need any more details.
Related
I’ve got a ReactJS website in which I am trying to use "#azure/msal-react": "^1.0.0-beta.1", and ran into some issues that have me flummoxed.
Most of my pages are open to the public. Some require login. If I add the MSALAuthenticationTemplate as below (but with interactionType=Redirect), as soon as I start the site, it asks me to login. I thought it would only do that if I hit a route that was in the AuthenticationTemplate.
Using InteractionType Popup causes the SPA to throw an exception on startup
Error: Objects are not valid as a React child (found: object with keys {login, result, error}). If you meant to render a collection of children, use an array instead. in p (at App.tsx:44)
All of my routes are, for some reason, coming back to the home page instead of loading the relevant components, event with the AuthenticationTemplate commented out.
I had this pretty much working using straight Javascript, but was running into ESLint issues when publishing, so I thought Typescript would help me fix those. But now it’s just broke.
render() {
initializeIcons();
return (
<MsalProvider instance={msalClient} >
<div className="d-flex flex-column h-100">
<TopMenu />
<div className="container-fluid flex-grow-1 d-flex">
<div className="row flex-fill flex-column flex-sm-row">
<BrowserRouter>
<MsalAuthenticationTemplate
interactionType={InteractionType.Popup}
errorComponent={this.ErrorComponent}
loadingComponent={this.LoadingComponent}>
<Switch>
<Route path="/addevent">
<AddEvent />
</Route>
<Route path="/mydashboard">
<MyDashboard />
</Route>
</Switch>
</MsalAuthenticationTemplate >
<UnauthenticatedTemplate>
<Switch>
<Route path='/'>
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/contactus">
<ContactUs />
</Route>
<Route path="/faq">
<Faq />
</Route>
<Route path="/fetchevents">
<FetchEvents />
</Route>
<Route path="/gettingstarted">
<GettingStarted />
</Route>
<Route path="/partners">
<Partners />
</Route>
<Route path="/privacypolicy">
<PrivacyPolicy />
</Route>
<Route path="/sponsors">
<Sponsors />
</Route>
<Route path="/termsofservice">
<TermsOfService />
</Route>
<Route path="/userstories">
<UserStories />
</Route>
</Switch>
</UnauthenticatedTemplate>
<div>
<Footer />
</div>
</BrowserRouter>
</div>
</div>
</div>
</MsalProvider>
);
Let's start with the UnauthenticatedTemplate. If the user is authenticated, children of the component will not show. So I guess you don't want to use it there. It's typical usage is for Login/Logout button for example.
Another problem is that if you are using MsalAuthenticationTemplate as the parent of the Switch and Route components. The problem is that you are guarding switch and routes from unauthenticated users, but this components should always be available without authentication, if you don't want to protect whole page.
During rendering React will go through your components one by one and first child of the BrowserRouter component it will try to render is MsalAuthenticationTemplate and since user is not authenticated, it will redirect user to login page.
This is quote from react-router docs:
A Route is always technically “rendered” even though it’s rendering null. When the 's path matches the current URL, it renders its children (your component).
Because of this the children of the route will only be rendered if the route will be hit. So you need to put MsalAuthenticationTemplate component as a direct child of the route, or even inside such component:
<Switch>
<Route path="/addevent">
<MsalAuthenticationTemplate
interactionType={InteractionType.Redirect}
authenticationRequest={loginRequest}
>
<AddEvent />
</MsalAuthenticationTemplate>
</Route>
...
</Switch>
As for all the webpages redirected to your home screen, you should add exact keyword to your Home route. This attribute causes it to not match all other routes also. Single '/' matches all your other routes.
In addition to the answer already provided, there is a way (cleaner in my opinion) you can configure MSAL react to take advantage of the router's navigate functions when MSAL redirects between pages in your app.
Here is how it works:
In your index.js file you can have something like so:
import { PublicClientApplication, EventType } from "#azure/msal-browser";
import { msalConfig } from "./authConfig";
export const msalInstance = new PublicClientApplication(msalConfig);
ReactDOM.render(
<React.StrictMode>
<Router>
<ThemeProvider theme={theme}>
<App pca={msalInstance} />
</ThemeProvider>
</Router>
</React.StrictMode>,
document.getElementById('root')
);
As shown, you need to pass msal instance as props to your main App.
Then in your App.js where you setup your routes, you will need to do the following:
import { MsalProvider } from "#azure/msal-react";
import { CustomNavigationClient } from "NavigationClient";
import { useHistory } from "react-router-dom";
function App({ pca }) {
// The 3 lines of code below allow you to configure MSAL to take advantage of the router's navigate functions when MSAL redirects between pages in your app
const history = useHistory();
const navigationClient = new CustomNavigationClient(history);
pca.setNavigationClient(navigationClient);
return (
<MsalProvider instance={pca}>
<Grid container justify="center">
<Pages />
</Grid>
</MsalProvider>
);
}
function Pages() {
return (
<Switch>
<Route path="/addevent">
<AddEvent />
</Route>
<Route path="/mydashboard">
<MyDashboard />
</Route>
<Route path='/'>
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/contactus">
<ContactUs />
</Route>
<Route path="/faq">
<Faq />
</Route>
// your other routes
</Switch>
)
}
And here is the helper function used in App.js that enables navigation by overriding the the default function used by MSAL
import { NavigationClient } from "#azure/msal-browser";
/**
* This is an example for overriding the default function MSAL uses to navigate to other urls in your webpage
*/
export class CustomNavigationClient extends NavigationClient{
constructor(history) {
super();
this.history = history;
}
/**
* Navigates to other pages within the same web application
* You can use the useHistory hook provided by react-router-dom to take advantage of client-side routing
* #param url
* #param options
*/
async navigateInternal(url, options) {
const relativePath = url.replace(window.location.origin, '');
if (options.noHistory) {
this.history.replace(relativePath);
} else {
this.history.push(relativePath);
}
return false;
}
}
You can then use AuthenticatedTemplate on your private pages and UnauthenticatedTemplate on the public pages. For example if you have have addEvent.js (private) and Home.js (public), you will have each components like so:
export function Home() {
return (
<>
<AuthenticatedTemplate>
<p>Welcome Home - it's public</p>
</AuthenticatedTemplate>
</>
);
}
export function AddEvent() {
return (
<>
<UnauthenticatedTemplate>
<Typography variant="h6">
Add event - it is a private page
</Typography>
</UnauthenticatedTemplate>
</>
);
}
Here is a complete example on how to use react-router with msal react for your reference.
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>
In my React Application I need to reload the component when it comes to the path path="/". I know react-router helps us reload easily the current component but I really need this in my application for some styling purpose. In my application I have two paths:
<Route path="/" component={newRoute}/>
and <Route path="/gallery" component={GalleryImages}/>. So, whenever I move from GalleryImages to newRoute I need to reload the newRoute components otherwise the styles are not working. What is the way around here? Here's myApp.js now:
const newRoute = () => {
return (
<div id="colorlib-page">
<div id="container-wrap">
<div id="colorlib-main">
<Introduction/>
<About/>
<Projects/>
<Timeline/>
<Blog/>
</div>
</div>
</div>
)
}
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Sidebar/>
<Switch>
<Route path="/" component={newRoute} exact/>
<Route path="/gallery" component={GalleryImages} exact/>
<Route component={Error}/>
</Switch>
</div>
</BrowserRouter>
);
}
}
export default App;
Try to use class component instead of functional component
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.
In the application the navigation appears single in all the pages except the home which is '/'.
How do I prevent the navigation from appearing twice in the home. Here is a screenshot and the code for the react-router.
Screen Shot OF Double Menu In React-Router:
Here is the code:
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Navigator />
<Switch>
<Route path='/'exact strict component ={HomeIndex} />
<Route path='/Pricing' exact component ={Pricing} />
<Route component={Error404}/>
</Switch>
</div>
</BrowserRouter>
);
}
}
Once check your HomeIndex component, may be you are using <Navigator /> again inside HomeIndex component.