I try to use typescript to rewrite the example of 'Redirects (Auth)', but meet one problem that the function cannot receive the props as I expect.
Here is my code:
router setting and function of private router:
export default () => (
<Router>
<Switch>
<PrivateRoute exact path="/welcome" component={Welcome}/>
<Route component={NoMatch}/>
</Switch>
</Router>)
const PrivateRoute = (component: any, ...rest: Array<any>) => {
console.log(component, rest)
return (
<Route {...rest} render={props => (
isAuthenticated() ? (
<div>
<Header/>
<Sider/>
<div className="content slide-in">
<component {...props}/>
</div>
</div>
) : (
<Redirect to={{
pathname: '/',
state: { from: props.location }
}}/>
)
)}/>
)}
I expect that the param of component is the Component of 'welcome', and the param of rest are other params such as 'exact' and 'path', but actually get the params as in above image.
component:
rest:
Anyone can help me to solve this problem?
Many thanks!
Related
I want to implement the PrivateRoute HOC which will check if the user is logged in then render the corresponding component, otherwise redirect to the login page.
Here is the PrivateRoute HOC:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const PrivateRoute = ({ component, ...rest }) => (
<Route {...rest} render={(props) => (
sessionStorage.getItem('userToken') ? (
React.createElement(component, props)
) : (
<Redirect to={{
pathname: '/login',
}} />
)
)} />
);
export default PrivateRoute;
Here's how I'm using it:
import PrivateRoute from './components/HOC/PrivateRoute';
...
render() {
if (this.props.hasError) return <h1>Sorry, something went wrong...</h1>;
if (this.props.isLoading) return <h1>Loading...</h1>;
return (
<div className="App">
<Switch>
<PrivateRoute exact path="/" component={<Home /> } />
<Route exact path="/login" render={props => <Login {...props} />} />
<Route exact path="/registration" render={props => <Registration {...props} />} />
<PrivateRoute exact path="/account" component={<Account />} />
</Switch>
</div>
);
}
But it throws an error:
I find a GitHub thread where I find a possible answer for why it happens like that, but I don't know how to implement that answer for my case:
I tried some experimental ways to achieve that but everything fails.
React.createElement accepts a type or a React component before it invoked.
React.createElement(
type,
[props],
[...children]
)
The type argument can be either a tag name string (such as 'div' or 'span'), a React component type (a class or a function), or a React fragment type.
const Component = () => <>Hello</>;
// Good
React.createElement(Component)
React.createElement("div")
// Bad
React.createElement(<div/>)
In your case you use the "bad" version.
You should use cloneElement or fix it with a wrapper:
// Good
React.cloneElement(<Component />)
// Or make a wrapper
React.createElement(() => <Component />));
So finally:
// Change to clone
React.cloneElement(component, props)
// **OR** make a wrapper
<PrivateRoute exact path="/" component={() => <Home />} />
will try to be brief.
Dashboard component is rendering, but while hitting localhost:3000/dashboard/shipments nothing is rendering.
Not versed in the react, not sure if render={({ location })(Line 1) is causing problem.
Tried placing components/simply h4 tag in Route (Line2) but nothing working.
Necessary imports are done.
App.js
const pages = [
{
pageLink: '/dashboard',
view: Dashboard,
displayName: 'Dashboard',
showInNavbar: true,
exact: false
},....more routes.
return(
<Router>
<Route render={({ location }) => (//---------->Line 1
<React.Fragment>
<Navbar />
<Switch location={location}>
{pages.map((page, index) => {
return (
<Route
exact={page.exact}
path={page.pageLink}
component={page.view}
key={index}
/>
)
})}
<Redirect to='/' />
</Switch>
</React.Fragment>
)}
/>
</Router>
)
dashboard.js
export default function Dashboard() {
const authedUser = useSelector(state => state.authedUser);
let { path } = useRouteMatch();
if (!authedUser.loggedIn) return <Redirect to='/login' />
return (
<React.Fragment>
<section id='dashboard-component'>
<Sidebar />
<Switch>
<Route exact path={path}>
<h2>Dashboard</h2>
</Route>
<Route exact path={`/${path}/shipments`}><h4>sdsd</h4></Route>//------>Line 2
</Switch>
</section>
</React.Fragment>
)
}
You have a extra / at the start of your nested Route
<Route exact path={`/${path}/shipments`}><h4>sdsd</h4></Route>
Now path already return /dashboard. Writing path={`/${path}/shipments`} would make the route path as path={'//dashboard/shipments'}
You need to specify your child route like
<Route exact path={`${path}/shipments`}><h4>sdsd</h4></Route>
Working demo
I hope all you are ding good.
I am developing a react(typescript) application in which I have to handle authentication and authorization.
I am following this pattern.
IAuthContext (will be loaded during startup or when user change their state)
export interface IAuthContext {
isAuthenticated: boolean;
isInitialized: boolean;
user: firebase.User | null;
permissions: object;
landingPage: string;
isOnBoardingCompleted: boolean;
}
Routes.js
const routerConfig = [
{
key: "key_",
path: '/login',
component: Login,
isPrivate : false
},
{
key: "dashboard",
path: '/dashboard',
component: Frame,
content: AnalyticsDashBoard,
isPrivate : true
},
App.tsx
return (
<BrowserRouter>
<div>
<Switch>
{routes.map((route) =>{
return (route.isPrivate ?
<PrivateRoute {...route} exact/>
:
<Route path={route.path} component={route.component} key={route.key} exact/>)
})}
</Switch>
</div>
</BrowserRouter>
);
PrivateRoute.tsx
return (
<Route
{...rest}
render={(routeProps) =>
props.context.isAuthenticated ? (
<Component {...routeProps} content={props.content}/>
) : (
<Redirect
to={{
pathname: '/login',
state: { from: routeProps.location }
}}
/>
)
}
/>
);
Inside privateRoute I have access to role and permissions along with landing page(if user didnt complete registration after login, he has to be redirected to registration page). Also I need to give all possible combinations in privateroute based on authentication since it will load only once.
I tried to do this in private route
if (props.context.isAuthenticated && !props.context.isOnBoardingCompleted) {
console.log("Going to redirect here to onboard--", props.context.landingPage);
return <Route path={props.context.landingPage} component={OnBoarding}/>
}
But this is not working since only the URL is changing. Also if the user is done with onboarding, I might not have a Route for it since all router controls already created with first-time values.
Please advise me on how can I handle this? All I need to do it is to have some sort of interceptor where I route/redirect to pages based on dynamic context values.
Expectation:
Just like scenario above, there are multiple roles and permissions and all those conditions should be checked here.
<Route path={props.context.landingPage} component={OnBoarding}/> should be defined in your Router component.
You can use a nested ternary, though this can lead to readability issues.
return (
<Route
{...rest}
render={routeProps =>
props.context.isAuthenticated ? (
props.context.isOnBoardingCompleted ? (
<Component {...routeProps} content={props.content} />
) : (
<Redirect
to={{
pathname: "/landing", // or "/onboard" whatever the route is
state: { from: routeProps.location }
}}
/>
)
) : (
<Redirect
to={{
pathname: "/login",
state: { from: routeProps.location }
}}
/>
)
}
/>
);
May be you can organize your code like below. It will help you separate different routes and handles their role/auth checks in a clean readable manner.
function App() {
return (
<BrowserRouter>
<div>
<Switch>
<Route component={PublicContainer}/>
<Route component={PrivateContainer}/>
</Switch>
</div>
</BrowserRouter>
);
}
const PrivateContainer = () => (
<div>
<NavBar/>
<Switch>
//routes with auth checks
</Switch>
</div>
);
const PrivateContainer = () => (
<div>
<NavBar/>
<Switch>
//Free routes
</Switch>
</div>
);
I'm looking for a way to do some route protection with react-router-4. Looking at an example in the documentation, they create a Component which is rendered like this:
<PrivateRoute path="/protected" component={Protected} />
and the privateRoute component:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)
I don't really understand why I should want to pass the "Protected" component as a property and then have to take care of spreading all the ...props and ...rest.
Before I was reading this doc (and other example), I created the following code, which just nest the routes in another component which takes care of the authentication part.
Because my example (which seems to work perfectly well), looks way more simplistic, I must be missing something.
Are there any downsides on this approach?
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
import Nav from './Nav';
// dummy
const Auth = {
isAuthenticated: () => { return true; }
}
const Home = () => <h1>Home</h1>
const SignIn = () => <h1>SignIn</h1>
const About = () => <h1>About</h1>
class PrivateOne extends Component {
render() {
console.log(this.props);
return <h1>Private</h1>
}
}
const PrivateTwo = () => <h1>PrivateTwo</h1>
const PrivateThree = () => <h1>PrivateThree</h1>
const NotFound = () => <h1>404</h1>
const Private = ({isAuthenticated, children}) => {
return(
isAuthenticated ? (
<div>
<h1>Private</h1>
{children}
</div>
) : (
<Redirect to={{
pathname: '/sign_in',
}}/>
)
)
}
const App = () =>
<div>
<Router>
<div>
<Nav />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/sign_in" component={SignIn} />
<Private isAuthenticated={Auth.isAuthenticated()}> {/* or some state later on */}
<Route path="/private1" component={PrivateOne} />
<Route path="/private2" component={PrivateTwo} />
<Route path="/private3" component={PrivateThree} />
</Private>
<Route component={NotFound} />
</Switch>
</div>
</Router>
</div>
export default App;
Im trying to render two components within a private route using react router dom v4. This is possible using a normal Route but it does not seem to be the case when using a custom Route. I get the following error:
"Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
"
Custom route (Authenticated)
return (
<Route
{...rest}
render={props =>
this.currentUser()
? <Component currentUser={this.currentUser} {...props} />
: <Redirect
to={{
pathname: '/auth/login',
state: { from: props.location }
}}
/>
}
/>
)
Then in my routes i want something like this
return (
<div>
<Switch location={isModal ? this.prevLocation : location}>
<Authenticated path="/" exact component={Main} />
<Route path="/auth/register" exact component={Register} />
<Route path="/auth/login" exact component={Login} />
<Authenticated
path="/clients/:id/edit"
render={(props) => ( // Not working as expected. Works fine using Route instead of Authenticated
<div>
<Main />
<ClientEdit />
</div>
)}
/>
</Switch>
{isModal ?
<Authenticated
path='/clients/new'
component={ClientNew}
/>
: null}
{isModal ?
<Authenticated
path='/clients/:id/edit'
component={ClientEdit}
/>
: null}
</div>
);
I'm a little late, but for anyone still needing this, I found that this works for me.
export function PrivateRoute({ component: Component = null, render: Render = null, ...rest }) {
const authService = new AuthService();
return (
<Route
{...rest}
render={props =>
authService.isAuthenticated ? (
Render ? (
Render(props)
) : Component ? (
<Component {...props} />
) : null
) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
}
/>
);
}
And in my routes I use it like so:
<PrivateRoute
path="/some-route/edit"
render={props => <MyComponent {...props} isAwesome={true} />} />
In your protectedRoute component, you are not receiving or utilizing render prop which you are sending in the line:
render={(props) => (
<div>
<Main />
<ClientEdit />
</div>
)}
instead of using render send the component in component prop like :
component={(props) => (
<div>
<Main />
<ClientEdit />
</div>
)}
Also check react router's docs to see when to use component prop and when to use render prop. It would be much better if you can change your protectedRoute to handle both.
I think you need to create a custom component returning :
return(
<div>
<Main />
<ClientEdit />
</div>)
Then import it and use it in your authenticated component like this :
<Authenticated
path="/clients/:id/edit"
component={CustomComponent}
/>
Edit: you can also handle render prop in your Authenticated component if provided :
if (this.props.render && this.currentUser()) {
return(
<Route
{...rest}
render={this.props.render}
/>
} else {
return (
<Route
{...rest}
render={props => this.currentUser() ?
<Component currentUser={this.currentUser} {...props} /> :
<Redirect
to={{
pathname: '/auth/login',
state: { from: props.location }
}}
/>
}
/>
)
}
import React from 'react';
import { Route, Redirect } from "react-router-dom";
const PeivateRoute = ({ component: component, ...rest }) => {
return (
<Route
{...rest}
render = {props => (false ? <component {...props}/> : <Redirect to="/" />)}
/>
);
};
export default PeivateRoute;