ReactJS with React Router single page application SPA with separate login page - javascript

I am new to React and React Router. I have been trying to create a single page application that has a layout with navigation bars and content and a login page that does not load inside this layout. I have been searching the web and stackoverflow, but have not been able to figure this out. To create my app, I used "yarn create react-app appName" and use "yarn add" to add react router. How do I need to create my routes such that I have a login page with its own layout and a main layout where my content can be displayed.
Here is my index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
Here is my App.js:
import React, { Component } from 'react';
import Main from "./Main";
import Login, {fakeAuth} from "./components/Login";
import {
Route,
NavLink,
HashRouter,
Redirect,
withRouter,
Switch,
BrowserRouter
} from "react-router-dom";
class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Main} />
<Route path="/login" component={Login} />
</Switch>
</BrowserRouter>
);
}
}
export default App;
Here is my Login.js:
import React from 'react';
import {
Route,
NavLink,
HashRouter,
Redirect
} from "react-router-dom";
class Login extends React.Component {
constructor() {
super();
this.state = {
redirectToReferrer: false
}
this.login = this.login.bind(this);
}
login() {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true })
})
}
render() {
const { from } = this.props.location.state || { from: {
pathname: '/' } }
const { redirectToReferrer } = this.state;
if (redirectToReferrer) {
return (
<Redirect to={from} />
)
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
)
}
}
/* A fake authentication function */
export const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100);
}
};
export default Login
Here is my Main.js:
import React, { Component } from 'react';
import Navigation from './components/Navigation'
import LeftSidebar from './components/LeftSidebar';
import RightSidebar from './components/RightSidebar';
import Login, {fakeAuth} from "./components/Login";
import {
Route,
NavLink,
HashRouter,
Redirect,
withRouter,
Switch
} from "react-router-dom";
/* Home component */
const Home = () => (
<div>
<h2>Home</h2>
</div>
)
/* Category component */
const Category = () => (
<div>
<h2>Category</h2>
</div>
)
/* Products component */
const Products = () => (
<div>
<h2>Products</h2>
</div>
)
class Main extends Component {
render() {
return (
<HashRouter>
<div className="container container-fluid">
<Navigation/>
<div className="row">
<div className="col-md-3">
<LeftSidebar/>
</div>
<div className="col-md-6">
<Route exact path="/" component={Home} />
<Route path="/category" component={Category}/>
<PrivateRoute authed={fakeAuth.isAuthenticated} path='/products' component = {Products} />
</div>
<div className="col-md-3">
<RightSidebar/>
</div>
</div>
</div>
</HashRouter>
);
}
}
const PrivateRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated === true ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location }
}}
/>
)}
/>
);
};
const AuthButton = withRouter(
({ history }) =>
fakeAuth.isAuthenticated ? (
<p>
Welcome!{" "}
<button
onClick={() => {
fakeAuth.signout(() => history.push("/"));
}}
>
Sign out
</button>
</p>
) : (
<p>You are not logged in.</p>
)
);
export default Main;
My links to the routes are in my navigation component:
....
<ul>
<li><Link to="/">Homes</Link></li>
<li><Link to="/category">Category</Link></li>
<li><Link to="/products">Products</Link></li>
</ul>
....
When I click the Home, Category, or Products link, I want the component to load in the SPA layout in Main.js and I want Login.js to load in its own page. Right now, Home, Category, Products, and Login are loading in the main layout. What do I need to change about these routes or other part of my code to get this working?
Edit:
Added my auth code in the Main.js class.
Added Login.js class.
In addition to following some tutorials on this, I have been following the advice from this answer:
Login Page Separated From Single-page Application (SPA) in ReactJS
However, I am not quite getting it. I am not sure exactly what I need to add to make this work.
Here are images illustrating what I am trying to do:
Main.js:
main SPA layout
Login.js:
Login page
Current issue is login is loading in the main layout rather than its own page:
Do NOT want this

Related

react routing taking too long

import React, { Component } from "react";
import { Col, Image, Nav } from "react-bootstrap";
import { NavLink } from "react-router-dom";
class Sidebar extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
console.log("sidebar");
}
render() {
return (
<Col md={3} lg={2} className="sidebar">
<div className="sidebarNav">
<NavLink to="/admin/dashboard" className="active">
<span className="icon-dashboard"></span> DASHBOARD
</NavLink>
<NavLink to="/admin/deliveryagentlist">
<span className="icon-delivery-agent"></span> DELIVERY AGENTS
</NavLink>
<NavLink to="/admin/customerlist">
<span className="icon-customers"></span> CUSTOMERS
</NavLink>
</div>
</Col>
);
}
}
export default Sidebar;
The above is the code of sidebar in my project.
The issue I am facing is when I change the page by clicking on sidebar items routing takes place. But it takes too long to redirect the page
I am on the dashboard. I click the customer in the sidebar. then the URL in the browser will change from http://localhost:3000/admin/dashboard to http://localhost:3000/admin/customerlist within no time. But the page loading takes too long. The customer list page contains some initial consoled text in its componentDidMount, like componentDidMount() { console.log("hey. Customer"); this.getList();}
and I observed the text hey. Customer also get consoled too late. The API calling inside this.getList() is also taking longer to start.
I am providing my routing code. It may be linked with routing.
MainRouting.js
import React, { Component } from "react";
import { Switch, Route } from "react-router-dom";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
// Route
import PrivateRouteAdmin from "./PrivateRouteAdmin";
import PublicRoute from "./PublicRoute";
// admin
import Login from "../Component/admin/login/Login";
import AdminDashboard from "../Component/admin/dashboard/Dashboard";
import AdminCustomerTable from "../Component/admin/customer/CustomerTable";
import DeliveryAgentTable from "../Component/admin/DeliveryAgent/DeliveryAgentTable";
class MainRoute extends Component {
render() {
return (
<>
<Switch>
<PublicRoute exact path="/" component={Login} />
<PrivateRouteAdmin
path="/admin/dashboard"
component={AdminDashboard}
/>
<PrivateRouteAdmin
path="/admin/customerlist"
component={AdminCustomerTable}
/>
<PrivateRouteAdmin
path="/admin/deliveryagentlist"
component={DeliveryAgentTable}
/>
<PublicRoute path="/admin/login" component={Login} />
</Switch>
</>
);
}
}
export default MainRoute;
PrivateRouteAdmin
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { getLocalStorage } from "../common/helpers/Utils";
const PrivateRouteAdmin = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
getLocalStorage("adminInfo") ? (
<Component {...props} {...rest} />
) : (
<Redirect
to={{ pathname: "/admin/login", state: { from: props.location } }}
/>
)
}
/>
);
export default PrivateRouteAdmin;
PublicRoute
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { getLocalStorage } from "../common/helpers/Utils";
const PublicRoute = ({ component: Component, restricted, ...rest }) => {
return (
<Route
{...rest}
render={props =>
getLocalStorage("adminInfo") ? (
<Redirect to="/admin/dashboard" />
) : (
<Component {...props} />
)
}
/>
);
};
export default PublicRoute;

Refreshing page with onClick event returns page does not exist

I have a list of posts and when the user clicks on the title he should be navigated to the current post by using the post id as an url. My Link does changes the url, but it does not update the page and the user is not redirected so I am trying to hook an onClick event that also refreshes the page, but when I do that the url is no longer the id of the post and I get a 404 that the page does not exist. How can I refresh the page successfully after the user has clicked on the Link?
Here is my parent component:
import React, { Component } from 'react'
import { NavLink, Route, BrowserRouter } from 'react-router-dom';
import Planets from '../Planets/Planets.jsx';
import Signs from '../Signs/Signs.jsx';
import PlanetaryAspects from '../PlanetaryAspects/PlanetaryAspects.jsx';
import Houses from '../Houses/Houses.jsx';
class BlogCategoriesMenu extends Component {
render() {
return(
<div className="blog-categories-menu">
<BrowserRouter>
<div className="blog-categories-menu__items">
<NavLink to="/planets" className="blog-categories-menu__items-a" activeClassName="isActive">Planets</NavLink>
<NavLink to="/signs" className="blog-categories-menu__items-a" activeClassName="isActive">Signs</NavLink>
<NavLink to="/planetary-aspects" className="blog-categories-menu__items-a" activeClassName="isActive">Planetary Aspects</NavLink>
<NavLink to="/houses" className="blog-categories-menu__items-a" activeClassName="isActive">Houses</NavLink>
<div className="content-container">
<Route path="/planets" component={Planets}></Route>
<Route path="/signs" component={Signs}></Route>
<Route path="/planetary-aspects" component={PlanetaryAspects}></Route>
<Route path="/houses" component={Houses}></Route>
</div>
</div>
</BrowserRouter>
</div>
)
}
}
export default BlogCategoriesMenu;
Here is component that the user clicks:
import React, { Component } from 'react'
import axios from 'axios';
import Spinner from '../Spinner/Spinner';
import { Link, Switch } from 'react-router-dom';
class Planets extends Component {
state = {
posts: [],
currentPage: 1,
setCurrentPage: 1,
postsPerPage: 5,
loading: false
}
componentDidMount() {
const fetchPosts = async () => {
this.setState({...this.state, loading: true});
const res = await axios.get('https://app-endpoint/');
this.setState({...this.state, posts: res.data, loading: false});
};
fetchPosts();
}
reloadPage = () => {
window.location.reload()
}
render() {
const retrogradesPosts = this.state.posts.filter( (item) => {
return item.categories === 'retrogrades'
})
if(this.state.loading) {
return <Spinner/>
}
return(
<div className="planet-articles-container">
<div className="">
<div className="">
{retrogradesPosts.map(post => (
<div key={post._id} className="planet-articles-container__post">
<img className="planet-articles-container__post-img" src={post.picture} alt="avatar"/>
<div className="">
<div className="">
<h2 className="">{post.date}</h2>
<Link to={`/post/${post._id}`} onClick={this.reloadPage}> <p className="">{post.title}</p></Link>
<p className="planet-articles-container__post-text">{post.postContent.substring(0, 100) + "..."}</p>
</div>
</div>
</div>
))}
</div>
</div>
</div>
)
}
}
export default Planets;
and my App.js:
import React from 'react';
import { BrowserRouter, Route, Switch} from 'react-router-dom';
import Home from './components/Home/Home.jsx'
import Error from './components/Error/Error.jsx';
import Blog from './components//Blog//Blog.jsx';
import Post from './components/Post/Post.jsx';
const App = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/blog" component={Blog} exact/>
<Route path="/post/:id" component={Post} exact/>
<Route component={Error}/>
</Switch>
</BrowserRouter>
);
};
export default App;
Is the BlogCatergoriesMenu rendered using /blog?
If BlogCatergoriesMenu is rendered inside the App and this "App Component" is wrapped by BrowserRouter then remove the BrowserRouter in BlogCatergoriesMenu to make it work.
Your updated BlogCatergoriesMenu will look something like this
import React, { Component } from 'react'
import { NavLink, Route } from 'react-router-dom';
import Planets from '../Planets/Planets.jsx';
import Signs from '../Signs/Signs.jsx';
import PlanetaryAspects from '../PlanetaryAspects/PlanetaryAspects.jsx';
import Houses from '../Houses/Houses.jsx';
class BlogCategoriesMenu extends Component {
render() {
return(
<div className="blog-categories-menu">
<div className="blog-categories-menu__items">
<NavLink to="/planets" className="blog-categories-menu__items-a" activeClassName="isActive">Planets</NavLink>
<NavLink to="/signs" className="blog-categories-menu__items-a" activeClassName="isActive">Signs</NavLink>
<NavLink to="/planetary-aspects" className="blog-categories-menu__items-a" activeClassName="isActive">Planetary Aspects</NavLink>
<NavLink to="/houses" className="blog-categories-menu__items-a" activeClassName="isActive">Houses</NavLink>
<div className="content-container">
<Route path="/planets" component={Planets}></Route>
<Route path="/signs" component={Signs}></Route>
<Route path="/planetary-aspects" component={PlanetaryAspects}></Route>
<Route path="/houses" component={Houses}></Route>
</div>
</div>
</div>
)
}
}
export default BlogCategoriesMenu;

How properly setup react-router-dom in Home Route?

I have this index.js:
<Provider store={store}>
<Router history={history}>
<App/>
</Router>
</Provider>
this App.js:
<Switch>
<Route exact path="/" component={Home} />
<Route
path="/login"
render={() => <Login userError={this.state.userError} />}
/>
<Route path="/registration" component={Registration} />;
</Switch>
and Home.js:
<div className="Home">
<Header/>
<div className="content">
<Sidenav/>
<Switch>
<Route path="/friends" component={Friends}/>
</Switch>
<Feed/>
</div>
</div>
I want Friends component to be rendered inside content block, but now if I try to reach /friends route via Link I am getting blank page. If I set /friends Route in App.js, it will be OK, but I won't have it in my content class, because it will be another page.
May you give me a hand with that?
Also in feature I will be have more items to display in content, that's why I put Switch in Home.js
Thanks in advance!
Move your content class and <Friends>
The issue you're having is that the component Home is not rendering when you visit /friends because it will only render when you go to /
To fix this just move the Route into the App.js file, along with the content class into the Friends component.
To make this easier, you could make your content class into a component. This way you could wrap it around all of the stuff you render.
Or move <Friends> and wrap content
What I mean by this is that you could also create your own Route component that wraps whatever component passed to it in a Content component. It might look similar to this:
const ContentRoute = ({ component, ...props }) => (
<Route {...props} component={() => (
<Content>
<component />
</Content>
)}>
</Route>
)
You can access demo here
Here what I have done. This demonstrates how to set layout when page's changing.
- src/
-- components/
--- Header.js
--- Sidenav.js
-- pages/
--- Home.js
--- Login.js
--- withBase.js
-- App.js
-- BaseLayout.js
-- routes.js
At first, let's make dummy components.
components/Header
import React from 'react';
export default () => (
<div>
This is Header.
</div>
);
components/Sidenav
import React from 'react';
export default () => (
<div>
This is Sidenav.
</div>
);
Then, pages.
pages/Home
import React from 'react';
import { NavLink } from 'react-router-dom';
import withBase from './withBase';
const Home = () => (
<div>
<p>Welcome Home!!!</p>
<NavLink to="/login">Go to login page</NavLink>
</div>
);
export default withBase(Home);
pages/Login
import React from 'react';
import { NavLink } from 'react-router-dom';
import withBase from './withBase';
const Login = () => (
<div>
<p>You have to login here...</p>
<NavLink to="/">Go home</NavLink>
</div>
);
export default withBase(Login);
pages/withBase
import React from 'react';
export default WrappedComponent => (
class extends React.Component {
componentDidMount() {
this.props.showHeaderSidenav();
}
render() {
return <WrappedComponent />;
}
}
);
As you see, withBase is a HOC. It runs showHeaderSidenav when the page is mounted.
App
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import BaseLayout from './BaseLayout';
import routes from './routes';
export default class extends React.Component {
state = {
withHeaderSidenav: true
}
showHeaderSidenav = (withHeaderSidenav = true) => {
this.setState({ withHeaderSidenav });
}
render() {
return (
<BaseLayout withHeaderSidenav={this.state.withHeaderSidenav}>
<Switch>
{routes.map(route => (
<Route
exact
key={route.path}
path={route.path}
render={() => (
<route.component
showHeaderSidenav={() => this.showHeaderSidenav(route.withHeaderSidenav)}
/>
)}
/>
))}
</Switch>
</BaseLayout>
);
}
}
BaseLayout
import React from 'react';
import Header from './components/Header';
import Sidenav from './components/Sidenav';
export default ({ withHeaderSidenav, children }) => (
<div>
{withHeaderSidenav && <Header />}
<div className="content">
{withHeaderSidenav && <Sidenav />}
{children}
</div>
</div>
);
We can say that BaseLayout is like a wrapper. It contains dynamic components which will be shown based on withHeaderSidenav prop.
Finally...
routes
import Home from './pages/Home';
import Login from './pages/Login';
export default [
{
path: '/',
component: Home,
withHeaderSidenav: true
},
{
path: '/login',
component: Login,
withHeaderSidenav: false
},
];
You could have moved(declared) content component inside Friends component. I do not see the reason why content component should live outside of Friends component. You can declare content component inside any component that needs it.Content component does not have to mess with routing implementation

React App not routing to the link

I am new to React and having issues with router. I am just learning from this tutorial: https://medium.com/#thejasonfile/basic-intro-to-react-router-v4-a08ae1ba5c42
Below is the code that is being called from my index.html. When I click on the link 'Show the list', the url changes from localhost:8080 to localhost:8080/list but doesn't really change the context of the page. I am not sure what is going or what I am doing wrong here. Any ideas?
Scripts.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Title, App} from './Components/App';
import { BrowserRouter as Router, Route } from 'react-router-dom';
ReactDOM.render(
<Router>
<div>
<Route exact path="/" component={Title} />
<Route path="/list" component={App} />
</div>
</Router>
, document.getElementById('app'));
App.js
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
const Title = () => {
return (
<div className="title">
<h1>React Router demo</h1>
<Link to="/list">
<button>Show the List</button>
</Link>
</div>
)};
const List = () => {
return (
<div className="nav">
<ul>
<li>list item</li><li>list item</li></ul><Link to="/"><button>Back Home</button></Link></div>)
}
module.exports = {
Title,
List
};
I refactored your code a bit, you should not render the App component for a single page rather your app component should have all the routes like how I made it below. Then as needed add Link throughout your components when you need to navigate and then add the routes in App respectively.
import React, { Component } from 'react';
import { render } from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import { Link } from 'react-router-dom';
class App extends Component {
constructor() {
super();
}
render() {
return (
<Router>
<div>
<Route exact path='/home' render={()=> <Title />} > </Route>
<Route exact path='/list' render={() => <List />} > </Route>
</div>
</Router>
);
}
}
const Title = () => {
return (
<div className="title">
<h1>React Router demo</h1>
<Link to="/list">
<button>Show the List</button>
</Link>
</div>
)};
const List = () => {
return (
<div className="nav">
<ul>
<li>list item</li><li>list item</li></ul><Link to='/home'><button>Back Home</button></Link></div>)
}
render(<App />, document.getElementById('app'));
document.getElementById is usually root looks like you changed it to app which is fine.

Redirect to login page using react-router v4

I'm new in React.
I want to create an amdin app that redirects to login by default (the main menu is hidden). Using react-router v4, based on this sample the menu is already shown by default. What is the best practice to do this? Here is my current code
in index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter,
Route,
Switch
} from 'react-router-dom';
import createHistory from 'history/createBrowserHistory'
import registerServiceWorker from './registerServiceWorker';
import './index.css';
import App from './App';
import Login from './modules/Login';
ReactDOM.render((
<BrowserRouter history={createHistory}>
<Switch>
<Route path="/login" component={Login} />
<Route path="/" component={App} />
</Switch>
</BrowserRouter>
), document.getElementById('root'));
registerServiceWorker();
in app.js
import React from 'react'
import {
BrowserRouter,
Link,
Switch,
Route,
Redirect
} from 'react-router-dom'
import { routes } from './router-config'
import Login from './modules/Login'
import fakeAuth from './Auth'
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)
)} />
);
const App = () => (
<BrowserRouter>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/protected">Protected</Link></li>
</ul>
<Switch>
{routes.map((route,index) => (
<PrivateRoute key={index} path={route.pattern} component={route.component} exact={route.exactly} />
))}
</Switch>
</div>
</BrowserRouter>
)
export default App
in login.js
import React, { Component } from 'react'
import {
Redirect
} from 'react-router-dom'
import fakeAuth from '../Auth'
import App from '../App'
class Login extends Component {
state = {
redirectToReferrer: false
}
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true })
})
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
const { redirectToReferrer } = this.state
if (fakeAuth.isAuthenticated) {
return (
<Redirect to={"/"}/>
)
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
)
}
}
export default Login;
i able to click login and redirect to App, but when i refresh the page when in App, the url is changed to /login but the display is still in the App. Please help. Thanks a lot
I solved this problem by creating component and load it in App
here is my code in app.js:
import React from 'react'
import {
BrowserRouter,
Link,
Switch,
Route,
Redirect,
withRouter
} from 'react-router-dom'
import { routes } from './router-config'
import Login from './modules/Login'
import fakeAuth from './Auth'
import About from './modules/About'
import Protected from './modules/Protected'
import './App.css';
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)
const AuthButton = withRouter(({ history }) => (
fakeAuth.isAuthenticated ? (
<div>
Welcome! <button onClick={() => {
fakeAuth.signout(() => history.push('/login'))
}}>Sign out</button>
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
</div>
) : (
<Redirect to={{
pathname: '/login'
}}/>
)
))
const App = () => (
<BrowserRouter>
<div>
<AuthButton/>
<Route path="/login" component={Login}/>
<PrivateRoute path="/about" component={About}/>
<PrivateRoute path="/protected" component={Protected}/>
</div>
</BrowserRouter>
)
export default App
Login.js
import React, { Component } from 'react'
import {
Redirect
} from 'react-router-dom'
import fakeAuth from '../Auth'
class Login extends React.Component {
state = {
redirectToReferrer: false
}
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true })
})
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
const { redirectToReferrer } = this.state
if (redirectToReferrer) {
return (
<Redirect to={from}/>
)
}
return (
<div>
<p>Please Login</p>
<div>
</div>
<Button onClick={this.login} type="primary">Log in</Button>
</div>
)
}
}
export default Login;
I don't really know if it's the best way to do this.

Categories