I'm trying to pass several props in a private route. What's the correct way to write this and what am I missing? Here is the code I have. My app works with this code, in that the user is able to login and see the dashboard. However, the props aren't passing. Is there a way to pass props to a private route?
<PrivateRoute exact path="/dashboard" component={Dashboard} render={routeProps =>
<Dashboard
handleUpdate={this.handleUpdate}
book={this.state.book}
info={this.state.info}
{...routeProps} />}
/>
Dashboard Component
class Dashboard extends Component {
state = {
book: this.props.book,
info: this.props.info,
error: '',
}
onLogoutClick = e => {
e.preventDefault();
this.props.logoutUser();
};
render() {
console.log(`BOOK STATE IN DB: ${this.state.book}`)
const { user } = this.props.auth;
return(
<div>
<h4>
<b>This is your page</b> {user.name}
</h4>
<button onClick={this.onLogoutClick}>Logout</button>
<h2>Search Book</h2>
<Search
handleUpdate={this.props.handleUpdate}
/>
<h4>Book Results</h4>
<div>{this.state.book}</div>
</div>
);
}
}
Dashboard.propTypes = {
logoutUser: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(
mapStateToProps,
{ logoutUser }
)(Dashboard);
Private Route
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
const PrivateRoute = ({ component: Component, auth, ...rest }) => (
console.log(auth),
<Route
{...rest}
render={props =>
auth.isAuthenticated === false ? (
<Redirect to="/login" />
) : (
<Component {...props} />
)
}
/>
);
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
Can you show us the code of PrivateRouter component? You can just follow the such way
<PrivateRoute exact path="/dashboard" component={Dashboard} props = {{book: this.state.book etc}}/>
And receive this props on PrivateRoute components to put it into child component
Can you try removing the component={Dashboard} prop, and only use the render prop to render the Dashboard. Your code should look like this
<PrivateRoute exact path="/dashboard" render={routeProps =>
<Dashboard
handleUpdate={this.handleUpdate}
book={this.state.book}
info={this.state.info}
{...routeProps} />}
/>
From the docs
Warning: <Route component> takes precedence over <Route render> so don’t use both in the same .
So, remove the component={Dashboard}
After the comments and the PrivateRoute code, i suggest you rewrite your PrivateRoute to
const PrivateRoute = ({ auth, ...rest }) => {
if (!auth.isAuthenticated) {
return <Redirect to="/login" />;
}
return <Route {...rest} />
);
and remove the component={Dashboard} part.
const PrivateRoute = ({component: Component, auth, book, handleUpdate, ...rest }) => (
console.log(rest),
console.log(book),
<Route
{...rest}
render={props =>
auth.isAuthenticated === false ? (
<Redirect to="/login" />
) : (
<Component book={book} handleUpdate={handleUpdate} {...props} />
)
}
/>
)
Related
In my typescript react project i have created a protected route to check for authenticationbefore rendering a component.
export const ProtectedRoute: React.FC<ProtectedRouteProps> = props =>{
const currentLocation = useLocation();
let redirectpath = props.redirectPathOnAuthentication;
console.log(redirectpath)
console.log('in protected route')
if(!props.isAuthenticated){
props.setRedirectPathOnAuthentication(currentLocation.pathname);
redirectpath = props.authenticationPath;
}
if(redirectpath !== currentLocation.pathname){
const renderComponent = () => <Redirect to={{pathname: redirectpath}} />;
return <Route {...props} component={renderComponent} render={undefined} />
}else{
return <Route {...props} />
}
}
I pass in the props to make conditional rendering based, on rather the user is authenticated or not.
I have the tries to access a ProtectedRoute, the user will be redirected to the route where login is possible ( named as /home route), and then redirected back to the original route.
export const RoutingHandler: React.FC = (props: Props) => {
const location = useLocation()
const [sessionContext, updateSessionContext] = useSessionContext()
console.log(location)
const setRedirectPathOnAuthentication = (path: string) =>{
updateSessionContext({...sessionContext, redirectPathOnAuthentication: path})
}
const defaultProtectedRouteProps: ProtectedRouteProps = {
isAuthenticated: !!sessionContext.isAuthenticated,
authenticationPath: '/home',
redirectPathOnAuthentication: sessionContext.redirectPathOnAuthentication || '',
setRedirectPathOnAuthentication
}
console.log(defaultProtectedRouteProps)
return (
<div>
<Navbar />
<Switch>
<ProtectedRoute {...defaultProtectedRouteProps} path="/dashboard" component={Dashboard} />
<Route exact path="/home" component={Home} />
<Route path="/about" component={About} />
<Route path="/help" component={Help} />
<Redirect from="/" to="/home" />
</Switch>
</div>
)
}
Whenever I do `history.push('/dashboard');` the `Dashboard` component is never rendered.
For desired implementation, you can try this approach, that works fine:
export const PrivateRoute = ({component: Component, isAuthenticated, ...rest}) => (
<Route {...rest}
render={
props => (
isAuthenticated ? (
<Component {...props} />
):
(
<Redirect to={{ pathname: '/login', state: { from: props.location }}}/>
)
)
}
/>
)
PrivateRoute.propTypes = {
isAuthenticated: PropTypes.bool.isRequired
}
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps)(PrivateRoute)
so, in DefaultsRoutes:
<Switch>
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<PrivateRoute component={Home} />
<Route path="*" component={NotFound404} />
</Switch>
in App:
let AppRedirect = ({ loadingApp }) => {
return loadingApp ? <Loading /> : <DefaultRoutes />;
};
const mapState = (state) => ({
loadingApp: state.app.loadingApp,
});
AppRedirect = connect(mapState)(AppRedirect);
export class App extends React.Component {
render() {
return (
<Provider store={store}>
<AppRedirect />
</Provider>
);
}
}
export default App;
When user successfully logged in dispatch an action to change state: loadingApp: true -> loadingApp: false
See the below function i am creating the Auth routes and getting the children undefined and shows blank page. In App.js i am using the private route as you can see below and when i use simple Route instead of PrivateRoute its shows the Login component
<PrivateRoute exact path="/" name="Login" render={props => <Login {...props}/>} />
Here is is My PrivateRoute.js. When i console the children its shows undefined
function PrivateRoute({ children, ...rest }) {
const token = cookie.get('token');
return (
<Route
{...rest}
render={({ location }) =>
!token ? (
children
) : (
<Redirect
to={{
pathname: "/dashboard",
state: { from: location }
}}
/>
)
}
/>
);
}
export default Private Route;
I usually use something like it. Let me know if it works for you.
Here is my privateRoute.js component:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isAuthenticated } from 'auth';
export default function PrivateRoute({ component: Component, ...rest }) {
return (
<Route {...rest} render={
props => (
isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
)} />
);
}
Here I have another file in the root auth.js:
export const isAuthenticated = () => {
return Boolean(localStorage.getItem('jwttoken'));
}
export const login = ({email, password}) => {
// Logic
localStorage.setItem('jwttoken', 'jkdsalkfj');
return true;
}
export const logout = () => {
localStorage.removeItem('jwttoken');
}
You can call it like:
<PrivateRoute
component={ProjectPage}
path="/project/:id"
/>
Children refer to what's inside the tag
<PrivateRoute>
<PrivateComponent/>
</PrivateRouter>
Here your children would be <PrivateComponent/>
You're not passing anything inside your PrivateRoute in your example. So you'll have undefined
I am trying to hide the Menu component when I select log in my app. I am working with react hooks and I have no idea how to do it.
My main looks like this :
<div>
<Menu/>
<Router>
{
domainList === "error" ?
(
<ErrorMessage
message="Error"
/>
)
:
Boolean(domainList) === true ?
(
<Main
endpoint={endpoint}
callbackReFetchDomains={reFetchDomains}
domainList={domainList}
hasDomainListError={hasDomainListError}
appendDomainList={appendDomainList}
changeDomainList={changeDomainList}
/>
)
:
(
<LoadingSpinner/>
)
}
</Router>
</div>
My main looks like this :
<>
<div>
{/*Switch will only render the first matched <Route/> child.*/}
<Menu/>
<Switch>
<Route path="/topics">
<ExampleComponentStructure/>
</Route>
<Route path="/login">
<Login/>
</Route>
<Route path="/domains">
<DomainList
endpoint={props.endpoint}
callbackReFetchDomains={props.callbackReFetchDomains}
domainList={props.domainList}
hasDomainListError={props.hasDomainListError}
appendDomainList={props.appendDomainList}
changeDomainList={props.changeDomainList}
/>
</Route>
<Route path="/signup">
<Signup/>
</Route>
<Route path="/users">
<UserMaintainList
endpoint={props.endpoint}
/>
</Route>
<Route path="/">
<StickerList
endpoint={props.endpoint}
callbackReFetchDomains={props.callbackReFetchDomains}
domainList={props.domainList}
hasDomainListError={props.hasDomainListError}
changeDomainList={props.changeDomainList}
/>
</Route>
</Switch>
</div>
</>
I know that the code is not clean. I am just starting with react and need some help doing this login screen. Thank you.
If i understood correct, you want to hide component Menu (is it navbar?). First you can check url you are in, by creating some flag, for example:
const isLogin = props.match.path === "/login"
And then just render component if it is false
{!isLogin && <Menu/>}
here is how i done
<PublicRoute exact path="/welcome" component={WelcomePageView} />
<PublicRoute exact path="/signin" component={SignInView} />
<PublicRoute
exact
path="/forgotPassword"
component={ResetPasswordView}
/>
<PublicRoute
exact
path="/resetPassword"
component={SetNewPasswordView}
/>
<RouteWithLayout
component={UserDashboardView}
exact
layout={MainLayout}
noAccess={true}
path="/dashboard"
/>
<RouteWithLayout
component={ProfileView}
exact
layout={MainLayout}
path="/Profile"
/>
PublicRoute.jsx
import React, { useEffect } from "react"
import { Route, Redirect } from "react-router"
import { connect } from "react-redux"
import { isUserAuthenticated, getAuthErrors, getAccessToken, getUserID } from "../selectors"
import { validateToken, logout } from "../actions/auth"
import { getPersistStatus } from "../selectors/persist"
const PublicRoute = ({
component: Component,
isAuthenticated,
authErrors,
access_token,
uid,
persistStatus,
logoutUser,
...rest
}) => {
useEffect(() => {
if (persistStatus && !isAuthenticated) {
logoutUser()
}
}, [persistStatus, isAuthenticated, logoutUser])
return (
<Route {...rest} render={props => (
!isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to={{
pathname: "/dashboard",
state: { from: props.location }
}} />
)
)} />
)
}
const mapStateToProps = (state) => ({
isAuthenticated: isUserAuthenticated(state),
authErrors: getAuthErrors(state),
access_token: getAccessToken(state),
uid: getUserID(state),
persistStatus: getPersistStatus(state)
})
const mapDispatchToProps = (dispatch) => ({
logoutUser: () => {
dispatch(logout())
}
})
export default connect(mapStateToProps, mapDispatchToProps)(PublicRoute)
RouteWithLayout.jsx
import React from "react"
import { Route } from "react-router-dom"
import PropTypes from "prop-types"
import { useAuthorization } from "./Permissions"
const RouteWithLayout = (props) => {
const { layout: Layout, component: Component, path, noAccess, ...rest } = props
const userAccess = useAuthorization(path)
return (
<Route
{...{ path, ...rest }}
render={(matchProps) => (
<Layout>
{noAccess || userAccess.includes("R") ? (
<Component {...matchProps} />
) : (
<p>You do not have sufficient permissions to view this page</p>
)}
</Layout>
)}
/>
)
}
RouteWithLayout.propTypes = {
component: PropTypes.any.isRequired,
layout: PropTypes.any.isRequired,
path: PropTypes.string,
}
export default RouteWithLayout
I am using this HOC to guard my routes but I find odd using this HOC in every component because I am already using 1 or 2 HOC's like reduxForm etc
import React from "react";
import { connect } from "react-redux";
export default ChildComponent => {
class ComposedComponent extends React.Component {
componentDidMount() {
this.shouldNavigateAway();
}
componentDidUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.auth) {
this.props.history.push("/");
}
}
render() {
return <ChildComponent {...this.props} />;
}
}
const mapStateToProps = state => {
return { auth: state.auth };
};
return connect(mapStateToProps)(ComposedComponent);
};
The HoC approach is right, but you should apply it to routes, not components.
Take a look at the pattern used in redux-auth-wrapper
I do not know how you implement your routes but there is clean solution for this.
render() {
let routes = (
<Switch>
<Route path="/auth" component={asyncAuth} />
<Route path="/" exact component={BurgerBuilder} />
<Redirect to="/" />
</Switch>
);
if (this.props.isAuthenticated) {
routes = (
<Switch>
<Route path="/checkout" component={asyncCheckout} />
<Route path="/orders" component={asyncOrders} />
<Route path="/logout" component={Logout} />
<Route path="/auth" component={asyncAuth} />
<Route path="/" exact component={BurgerBuilder} />
<Redirect to="/" />
</Switch>
);
}
return (
<div>
<Layout>
{routes}
</Layout>
</div>
);
}
And store the auth token in your redux.
const mapStateToProps = state => {
return {
isAuthenticated: state.auth.token !== null
};
};
In my App.js, I have the following:
const Index = asyncRoute(() => import('~/pages/index'))
const Register = asyncRoute(() => import('~/pages/register'))
const AddDesign = asyncRoute(() => import('~/pages/add-design'))
const Login = asyncRoute(() => import('~/pages/login'))
class App extends Component {
render() {
const { isLoggedIn } = this.props;
if(!isLoggedIn){
return (
<Switch>
<Route path={'/login'} component={Login} />
<Route path={'/register'} component={Register} />
<Redirect to={'/login'} />
</Switch>
);
}
return (
<Switch>
<Route exact path='/' component={Index}/>
<Route exact path='/add-design' component={AddDesign}/>
<Route exact path="/login" render={() => <Redirect to="/"/>} />
<Route exact path="/register" render={() => <Redirect to="/"/>} />
<Redirect to={'/'} />
</Switch>
);
}
}
function mapStateToProps({ user }) {
return {
isLoggedIn: !!user.token,
};
}
export default connect(mapStateToProps)(App);
When the user logs in, isLoggedIn is set to true and it then attempts to redirect the user back to "/"
This happens, however the page loaded is the index.html file within public, rather than the Index component.
I'm not sure if its making a difference, but my asyncRoute is a workaround for FOUC:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Loading from '~/components/Loading'
class AsyncImport extends Component {
static propTypes = {
load: PropTypes.func.isRequired,
children: PropTypes.node.isRequired
}
state = {
component: null
}
_hasClass(target, className) {
return new RegExp('(\\s|^)' + className + '(\\s|$)').test(target.className);
}
toggleFoucClass () {
const root = document.getElementById('root')
if (this._hasClass(root, 'fouc')) {
root.classList.remove('fouc')
} else {
root.classList.add('fouc');
}
}
componentWillMount () {
this.toggleFoucClass()
}
componentDidMount () {
this.props.load()
.then((component) => {
setTimeout(() => this.toggleFoucClass(), 1)
this.setState(() => ({
component: component.default
}))
})
}
render () {
return this.props.children(this.state.component)
}
}
const asyncRoute = (importFunc) =>
(props) => (
<AsyncImport load={importFunc}>
{(Component) => {
return Component === null
? <Loading size="large" />
: <Component {...props} />
}}
</AsyncImport>
)
export default asyncRoute
Can anyone explain why my users are being routed but the component not rendering?
Assuming using RR4
Try:
<Route path="/" component={App}>
<IndexRedirect to="/index" />
<Route path="index" component={Index} />
Refer to the following to get your usecase correct:
https://github.com/ReactTraining/react-router/blob/5e69b23a369b7dbcb9afc6cdca9bf2dcf07ad432/docs/guides/IndexRoutes.md