I'm trying to move from having current active companyId in query params to URL path.
http://localhost:3000/1?companyId=comp1
http://localhost:3000/2?companyId=comp1
To
http://localhost:3000/comp1/1
http://localhost:3000/comp1/2
So basically:
User Auth it's self in Login screen.
Then selects a company from list to work with / see data in Page1 and Page2
With specific company selected Page1 and Page2 should be loaded, and that company selected should continue to be in URL
http://localhost:3000/comp1/1
http://localhost:3000/comp1/2
Initial idea i had is to store selected company in state and then on state change change the basename but that does not work, i can't access Page*
Main App
import { Link, BrowserRouter as Router, Switch } from 'react-router-dom'
import { useStoreRoutes } from '../../states/useStoreRoutes'
import { PrivateRoutes } from './components/PrivateRoutes'
import { PublicRoutes } from './components/PublicRoutes'
export const ReactRouter = () => {
const currentOrg = useStoreRoutes((s) => s.currentOrg)
return (
<>
<Router basename={process.env.PUBLIC_URL + currentOrg}>
<div style={{ display: 'flex', gap: '10px' }}>
<Link to="/">Home</Link>
<Link to="/login">Login</Link>
<Link to="/compList">Comp List</Link>
<Link to="/1">Page 1</Link>
<Link to="/2">Page 2</Link>
</div>
<RoutesComp />
</Router>
</>
)
}
const RoutesComp = () => {
return (
<Switch>
<PrivateRoutes component={Page1} path="/1" />
<PrivateRoutes component={Page2} path="/2" />
<PublicRoutes component={Login} path="/login" />
<PublicRoutes component={CompList} path="/compList" />
<PublicRoutes component={Home} path="/" />
</Switch>
)
}
const Home = () => <h2>Home</h2>
const Login = () => <h2>Login</h2>
const CompList = () => {
const currentOrgSet = useStoreRoutes((s) => s.currentOrgSet)
return (
<h2>
<Link to="/comp1" onClick={() => currentOrgSet('comp1')}>
Comp1
</Link>
<Link to="/comp2" onClick={() => currentOrgSet('comp2')}>
Comp2
</Link>
</h2>
)
}
const Page1 = () => <h2>Page 1</h2>
const Page2 = () => <h2>Page 2</h2>
State
import create from 'zustand'
export const useStoreRoutes = create((set: any) => {
return {
currentOrg: '',
currentOrgSet: (data: string) => set({ currentOrg: data }),
}
})
PrivateRoutes
import { Redirect, Route, RouteProps } from 'react-router-dom'
export const db = [
{
companyId: 'comp1',
},
{
companyId: 'comp2',
},
{
companyId: 'comp3',
},
]
export const PrivateRoutes = ({ component: Component, ...rest }: any) => {
const getCompId = window.location.pathname.split('/')[1]
const checkIfCompIsInUser = () => {
for (let i = 0; i < db.length; i++) {
const el = db[i]
if (el.companyId === getCompId) {
console.log(true)
return true
} else {
console.log(false)
return false
}
}
}
return (
<Route
render={(props: any) =>
checkIfCompIsInUser() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
}
{...rest}
/>
)
}
PublicRoute
import { Route, RouteProps } from 'react-router-dom'
export const PublicRoutes = ({ component: Component, ...rest }: any) => {
return <Route {...rest} render={(props: any) => <Component {...props} />} />
}
From what I can tell, you need to declare the Page1 and Page2 routes to use a dynamic path that includes the companyId.
const RoutesComp = () => {
return (
<Switch>
<PrivateRoutes component={Page1} path="/:companyId/1" />
<PrivateRoutes component={Page2} path="/:companyId/2" />
<PublicRoutes component={Login} path="/login" />
<PublicRoutes component={CompList} path="/compList" />
<PublicRoutes component={Home} path="/" />
</Switch>
);
};
From here you can fix the PrivateRoutes custom route component to read the route match params for checking access.
export const PrivateRoutes = ({ component: Component, ...rest }: any) => {
return (
<Route
render={(props: any) => {
const { companyId } = props.match.params;
const checkIfCompIsInUser = () => {
return db.some((el) => el.companyId === companyId);
};
return checkIfCompIsInUser() ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
);
}}
{...rest}
/>
);
};
Example CompanyList to render links to each company's page 1 and 2.
const CompList = () => {
return (
<ul>
<li>
<Link to="/comp1/1">Comp1</Link>
</li>
<li>
<Link to="/comp1/2">Comp2</Link>
</li>
<li>
<Link to="/comp2/1">Comp1</Link>
</li>
<li>
<Link to="/comp2/2">Comp2</Link>
</li>
</ul>
);
};
Related
I am using React Router v6 to build nested routes. I am facing 2 issues:
If I click a link which has children, the url should automatically go to the child, but only the component is rendered. The URL still says "/*".
Inside my child, I have a link which should get me the entire path. For example, it should be '/routeC/subC3/newRoute'
Please help.
This is my code.
App.js
import "./styles.css";
import {
Navigate,
Route,
Routes,
useMatch,
useLocation,
BrowserRouter,
Link,
Outlet
} from "react-router-dom";
import ComponentC from "./ComponentC";
import { Fragment } from "react";
const ComponentA = () => <p>Component A</p>;
const ComponentB = () => <p>Component B</p>;
const ComponentC1 = () => <p>I am in Component C1</p>;
const ComponentC2 = () => <p>I am in Component C2</p>;
const SubComponentC3 = () => <p>SubComponent C3</p>;
export const ComponentC3 = () => {
const location = useLocation();
const match = useMatch(location.pathname);
return (
<>
<p>Component C3</p>
<Link to={`${match.path}/newRoute`}>Take me to a new route</Link>
<Routes>
<Route
exact
path={`${match.path}/newRoute`}
element={<SubComponentC3 />}
/>
</Routes>
</>
);
};
export const componentCChildren = [
{
label: "Component C - 1",
code: "subC1",
component: ComponentC1
},
{
label: "Component C - 2",
code: "subC2",
component: ComponentC2
},
{
label: "Component C - 3",
code: "subC3",
component: ComponentC3
}
];
export const routeValues = [
{
label: "Component A",
path: "/routeA",
component: ComponentA,
children: []
},
{
label: "Component B",
path: "/routeB",
component: ComponentB,
children: []
},
{
label: "Component C",
path: "/routeC/*",
component: ComponentC,
children: componentCChildren
}
];
export default function App() {
return (
<div className="App">
<BrowserRouter>
{routeValues.map((item) => (
<Link key={item.path} to={item.path} style={{ paddingRight: "10px" }}>
{item.label}
</Link>
))}
<Routes>
{routeValues.map((route) => {
if (route.children.length > 0) {
return (
<Route
key={route.path}
path={route.path}
element={<route.component />}
>
{route.children.map((r, i, arr) => (
<Fragment key={r.code}>
<Route
path={`${route.path}/${r.code}`}
element={<r.component />}
/>
<Route
path={route.path}
element={<Navigate to={`${route.path}/${arr[0].code}`} />}
/>
</Fragment>
))}
</Route>
);
}
return (
<Route
key={route.path}
path={route.path}
element={<route.component />}
/>
);
})}
<Route path="*" element={<Navigate to="routeA" />} />
</Routes>
<Outlet />
</BrowserRouter>
</div>
);
}
ComponentC.js
import { useState } from "react";
import Tab from "#mui/material/Tab";
import Box from "#mui/material/Box";
import TabContext from "#mui/lab/TabContext";
import TabList from "#mui/lab/TabList";
import TabPanel from "#mui/lab/TabPanel";
import { useNavigate, useMatch, useLocation } from "react-router-dom";
import { componentCChildren } from "./App";
export default function ComponentC(props) {
const navigate = useNavigate();
const location = useLocation();
const match = useMatch(location.pathname);
const [tabId, setTabId] = useState(componentCChildren[0].code);
const handleTabChange = (e, tabId) => {
console.log("tabId", tabId);
navigate(`${tabId}`);
setTabId(tabId);
};
return (
<>
<p>Component C</p>
<TabContext value={tabId}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<TabList onChange={handleTabChange} aria-label="lab API tabs example">
{componentCChildren.map((tab) => {
return <Tab key={tab.code} value={tab.code} label={tab.label} />;
})}
</TabList>
</Box>
{componentCChildren.map((tab) => {
return (
<TabPanel key={tab.code} value={tab.code}>
{<tab.component />}
</TabPanel>
);
})}
</TabContext>
</>
);
}
This is a link to my sandbox.
Here's a refactor that leaves most of your route definitions in tact. The changes are mostly in how, and where, the routes are rendered.
App.js
Remove the routeValues children and change the "/routeC/*" string literal to "/routeC" since it's used for both the route path and the link. Append the "*" wildcard character to the route's path when rendering.
ComponentC3 will use relative links and paths to get to ".../newRoute" where "..." is the currently matched route path.
export const ComponentC3 = () => {
return (
<>
<p>Component C3</p>
<Link to="newRoute">Take me to a new route</Link>
<Routes>
<Route path="newRoute" element={<SubComponentC3 />} />
</Routes>
</>
);
};
export const routeValues = [
{
label: "Component A",
path: "/routeA",
component: ComponentA,
},
{
label: "Component B",
path: "/routeB",
component: ComponentB,
},
{
label: "Component C",
path: "/routeC",
component: ComponentC,
}
];
export default function App() {
return (
<div className="App">
<BrowserRouter>
{routeValues.map((item) => (
<Link key={item.path} to={item.path} style={{ paddingRight: "10px" }}>
{item.label}
</Link>
))}
<Routes>
{routeValues.map((route) => (
<Route
key={route.path}
path={`${route.path}/*`} // <-- append wildcard '*' here
element={<route.component />}
/>
))}
<Route path="*" element={<Navigate to="routeA" />} />
</Routes>
</BrowserRouter>
</div>
);
}
ComponentC.js
Here is where you'll render the componentCChildren as descendent routes. Within a new Routes component map componentCChildren to Route components each rendering a TabPanel component. Append the "*" wildcard matcher to the route path again so further descendent routes can be matched. Use a useEffect hook to issue an imperative redirect from "/routeC" to the first tab at "/routeC/subC1".
export default function ComponentC(props) {
const navigate = useNavigate();
useEffect(() => {
if (componentCChildren?.[0]?.code) {
// redirect to first tab if it exists
navigate(componentCChildren[0].code, { replace: true });
}
// run only on component mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [tabId, setTabId] = useState(componentCChildren[0].code);
const handleTabChange = (e, tabId) => {
console.log("tabId", tabId);
navigate(tabId, { replace: true }); // just redirect between tabs
setTabId(tabId);
};
return (
<>
<p>Component C</p>
<TabContext value={tabId}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<TabList onChange={handleTabChange} aria-label="lab API tabs example">
{componentCChildren.map((tab) => {
return <Tab key={tab.code} value={tab.code} label={tab.label} />;
})}
</TabList>
</Box>
<Routes>
{componentCChildren.map((tab) => {
const TabComponent = tab.component;
return (
<Route
key={tab.code}
path={`${tab.code}/*`} // <-- append wildcard '*' here
element={
<TabPanel value={tab.code}>
<TabComponent />
</TabPanel>
}
/>
);
})}
</Routes>
</TabContext>
</>
);
}
in ComponentC just you need to pass <Outlet />. i updated your working demo pls check here
the app function have a router and i well use them in the components thanks for help ....
function App() {
return (
<Router>
<div className='App'>
<Switch>
<Route exact path='/'>
<Login />
</Route>
<Route path='/SignUp'>
<SignUp />
</Route>
</Switch>
<Route
path='/Student/:id'
exact
render={({ match }) => <Student match={match} />}
/>
<Route
path='/Admin/:id'
exact
render={({ match }) => <Admin match={match} />}
/>
</div>
</Router>
);
}
export default App;
just need to use redirect after the admin decided to click on the logout button
without using usehistory.
const Admin = () => {
const logoutfunc = async () => {
let connectToServer = new ConnectToServer();
//let session=await connectToServer.getSession()
connectToServer.logout();
};
const redirect = () => {
return <Redirect to={{ pathname: '/' }} />;
};
return (
<div>
<button
onClick={() => {
logoutfunc();
redirect();
}}
>
logout
</button>
<Cards />
<TableStudent />
<Cohorts />
</div>
);
};
export default Admin;
what you have to do is this:
const Admin = () => {
const [redirect, setRedirect] = useState(false);
const logoutfunc = async () => {
let connectToServer = new ConnectToServer();
//let session=await connectToServer.getSession()
connectToServer.logout();
setRedirect(true);
};
if(redirect){
return <Redirect to={{ pathname: '/' }} />;
};
return (
<div>
<button
onClick={logoutfunc}
>
logout
</button>
</div>
);
};
or use push method like this:
const Admin = () => {
const logoutfunc = async () => {
let connectToServer = new ConnectToServer();
//let session=await connectToServer.getSession()
connectToServer.logout();
history.push({ pathname: '/' })
};
return (
<div>
<button
onClick={logoutfunc}
>
logout
</button>
</div>
);
};
I managed to make a private route and navigate to different pages using react-router-dom. How ever, when I navigate to a page and reload it, it first goes to /login for half a second and the reloads the page correctly. How can I prevent this unwanted behavior and improve my routing?
Here are my routes:
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
This is the full component:
import {
Route,
BrowserRouter as Router,
Link,
Redirect,
} from "react-router-dom";
import { Container, Button } from "#material-ui/core/";
import Login from "./Login";
import { useContext,useState } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const [path,setPath] = useState("/home")
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
setPath("/dashboard")
console.log("Dash");
return (
<Container>
<Link to="/home">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
const RedirectPage = () => {
if (!logging) {
return <div></div>;
} else {
return <Login />;
}
};
return (
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
);
};
export { Routes };
This is my Login component.
import { useState, useContext } from "react";
import {
Button,
Card,
Container,
Typography,
Box,
TextField,
} from "#material-ui/core/";
import { useHistory} from "react-router-dom";
import { signIn } from "../Storage/Auth";
import { UserContext } from "../App";
const Login = () => {
const [mail, setMail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const { user, setUser } = useContext(UserContext);
const handleSignIn = async (m: string, p: string) => {
await signIn(m, p).then((e) => {
console.log("USERID", e, user);
setUser(e);
});
};
const history = useHistory();
const handleEnter = () => {
history.push("/home");
};
const handleOnKey = (e: any) => {
if (e.key === "Enter") {
e.preventDefault();
handleSignIn(mail, password);
handleEnter();
}
};
return (
<Card className="Card" raised={true}>
<Container className="Input">
<Typography className="Sign-in" paragraph={true} variant="inherit">
Sign in
</Typography>
<Box
className="Box"
borderColor="error.main"
border={2}
borderRadius="borderRadius"
>
<Container>
<TextField
fullWidth={true}
placeholder=" email"
value={mail}
onChange={(e) => {
setMail(e.target.value);
}}
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
</Container>
<Container className="Input">
<Box
className="Box"
borderColor="error.main"
borderRadius="borderRadius"
border={2}
>
<Container>
<TextField
fullWidth={true}
placeholder=" password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
type="password"
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
<h1> </h1>
<Button
onClick={() => {
handleSignIn(mail, password);
}}
fullWidth={true}
color="primary"
variant="contained"
type="submit"
>
Sign In{" "}
</Button>
<h1> </h1>
<Box className="Sign-in">
<Button size="small"> Register </Button>
</Box>
<h1> </h1>
</Container>
</Card>
);
};
export default Login;
This is the App component:
import { useEffect } from "react";
import { Routes } from "./Routing/Routes";
import "./App.css";
import { Container } from "#material-ui/core/";
import initFirebase from "./Storage/Secret";
import { useState, createContext } from "react";
import { onAuthChange } from "./Storage/Auth";
export const UserContext = createContext<any>(null);
function App() {
const [user, setUser] = useState(null);
const [auth, setAuth] = useState<string | null>("");
const [logging, setLogging] = useState(null)
useEffect(() => {
initFirebase();
}, []);
useEffect(() => {
onAuthChange(setAuth,setLogging);
}, [auth]);
return (
<UserContext.Provider value={{ user, setUser, auth,setAuth,logging }}>
<div className="App">
<Container>
<Routes />
</Container>
</div>
</UserContext.Provider>
);
}
export default App;
Also, here is the auth logic:
import firebase from "firebase/app";
import "firebase/auth";
const auth = () => firebase.auth();
const signIn = async (email, password) => {
await auth()
.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
var user = userCredential.user;
console.log("USER", user);
return user.uid;
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
alert(errorCode, errorMessage);
return null;
});
};
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
const signOut = (setState) => {
auth()
.signOut()
.then(function () {
console.log("LOGGED OUT");
})
.catch(function (error) {
console.log("ERROR LOGGING OUT");
});
setState(null);
};
export { signIn, signOut, onAuthChange }
Finally, the full code is in https://gitlab.com/programandoconro/adminkanjicreator
Any suggestion will be appreciated, thanks.
I would recommend doing the auth check earlier. So something like this so that the routes themselves only get rendered if there is something in auth. I think your example is also missing the Switch statement which often helps.
<Router>
{!auth ? (
<Switch>
<Route exact path="/login" component={RedirectPage} />
</Switch>
) : (
<Switch>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
</Switch>
)}
</Router>
Typically you will want some sort of "loading" or "indeterminant" state to represent neither authenticated nor unauthenticated. You can use this third "state" to hold the UI before committing to rendering one way or the other on anything based upon authentication.
Since your auth logic resolves to a boolean true|false.
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
You can use the fact that the initial auth state is neither of these. I suggest using null.
const [auth, setAuth] = useState<string | null>(null);
When rendering the Route utilizing the auth state you can augment the logic to return early before deciding to redirect.
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
Note here that I've also switched over to the render prop, the component prop is intended for attaching actual React components. These are treated a little differently. You can read about the route render method differences here.
The full router example:
<Router>
<Switch>
<Route path="/home" component={Home} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/login" component={RedirectPage} />
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
</Switch>
</Router>
Note here that I've also included the Switch component and reordered the routes so the more specific paths are listed before less specific paths. This allows you to remove the unnecessary exact prop from all the routes since the Switch renders routes exclusively (versus inclusively as the Router does).
I finally managed to solve the issue. Now the reload works perfectly and the security was implemented as excepted. This is my final Router:
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
This is how the component looks like now.
import {
Route,
BrowserRouter as Router,
Link,
Redirect
} from "react-router-dom";
import { Container, Button } from "#material-ui/core/";
import Login from "./Login";
import { useContext, useState, useEffect } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const pathname = window.location.pathname;
const [path, setPath] = useState(pathname);
useEffect(() => {
console.log(path);
path === "/login" && setPath("/");
path !== "/" && path !== "/dashboard" && setPath("/");
}, [auth]);
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
console.log("Dash");
return (
<Container>
<Link to="/">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
return (
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
);
};
export { Routes };
Thanks #Richard and #Drew for their kind support.
I have a react router app:
export default () => (
<Router basename={process.env.REACT_APP_BASENAME || ""}>
<div>
{routes.map((route, index) => {
return (
<PrivateRoute
key={index}
path={route.path}
exact={route.exact}
component={props => {
return (
<route.layout {...props}>
<route.component {...props} />
</route.layout>
);
}}
/>
);
})}
</div>
</Router>
);
and this will render dfferent views based on the route clicked. the routes will render based on this object in a routes.js file:
export default [
{
path: "/login",
layout: DefaultLayout,
component: LogIn
}, .....]
To build in some authentication, I defined a PrivateRoute as:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to='/login' />
)} />
)
however, when i set the app as using PrivateRoute instead of normal Route (in the first snippet), the redirect does not use the routes object. How do I change the PrivateRoute const for a log in page reflect my original React Route architecture? what is the best practice?
Your code looks fine, but since you said your routes object is not understood by react-router maybe there is the case that your components aren't defined properly. For example, your components may be defined after the object is created. In that case, when that object is created, it will refer to undefined components. I made this mistake once, so I am just sharing what possibly went wrong.
Here is an example:
import ReactDOM from "react-dom";
import React, { Component } from "react";
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
function Public() {
return <h3>Public</h3>;
}
function Protected() {
return <h3>You can see protected content</h3>;
}
class Login extends Component {
state = { redirectToReferrer: false };
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
render() {
let { from } = this.props.location.state || { from: { pathname: "/" } };
let { 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>
);
}
}
const routes = [
{
path: "/public",
component: Public,
private: false
},
{
path: "/login",
component: Login,
private: false
},
{
path: "/protected",
component: Protected,
private: true
}
];
function AuthExample() {
return (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
{routes.map((route, index) => {
if (route.private)
return (
<PrivateRoute
key={index}
path={route.path}
exact={route.exact}
component={props => {
return <route.component {...props} />;
}}
/>
);
return (
<Route
key={index}
path={route.path}
exact={route.exact}
component={props => {
return <route.component {...props} />;
}}
/>
);
})}
</div>
</Router>
);
}
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100);
}
};
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>
)
);
function PrivateRoute(props) {
const { component: Component, ...rest } = props;
return (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<AuthExample />, rootElement);
Notice Public, Protected and Login components are defined above the routes object. Defining them after the routes will result in errors.
I suggest to change your private route as following
const PrivateRoute = ({ component: Component, ...rest }) => fakeAuth.isAuthenticated === true ? (
<Route {...rest} component={component}
)} />
) : <Redirect to='/login' />;
I have created a reactJS app using create-react-app using Flux as an architecture where I want to have some routes accessible without being authenticated and some only accessible while authenticated. Using the flux design pattern I am passing the state of the store down through each component using props so the store state is available for all child components that require it.
I have studied the documentation here (the example is also pasted below) to try and understand how to achieve the above outcome in my app.
I can't see how I can adapt the example to pass state down to the component called within the protected route without doing this with an explicit name like how component is passed.I want to acheive...
Pass the component to PrivateRoute so it can be called if my user is authenticated.
Pass all props from the parent component to the component called by PrivateRoute (this is to allow me to keep cascading the store state down through the props and also to check in the store state if the user is logged in).
I think I am perhaps misunderstanding something fundamental here. Can anyone advise please?
import React from "react";
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
////////////////////////////////////////////////////////////
// 1. Click the public page
// 2. Click the protected page
// 3. Log in
// 4. Click the back button, note the URL each time
const AuthExample = () => (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
<Route path="/public" component={Public} />
<Route path="/login" component={Login} />
<PrivateRoute path="/protected" component={Protected} />
</div>
</Router>
);
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100);
}
};
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>
)
);
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
const Public = () => <h3>Public</h3>;
const Protected = () => <h3>Protected</h3>;
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>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
);
}
}
export default AuthExample;
Here is a section from my actual code...
class Page extends React.Component{
// constructor(props) {
// super(props);
// }
render(){
return(
<Router>
<div>
<Route exact path="/" render={()=><HomePage {...this.props}/>}/>
<Route path="/login" render={()=><LoginPage {...this.props}/>}/>
<PrivateRoute exact path="/protected" component={Main} extra="Boo!"/>
</div>
</Router>);
}
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={(props) =>
(console.log(this.props.extra) || 1) ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
This.props.extra is undefined.
If you are looking for a way to pass extra props to the PrivateRoute you can do:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={ (props) =>
( console.log(props.extra) || 1) ? (
<Component {...props} {...rest} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
then
<PrivateRoute exact path="/protected" component={Main} extra="Boo!"/>
and Main should now receive extra prop (together with path and exact).