support render props of react router in route hoc - javascript

I am writing a HOC for public route and private route. If the route is private and the user is authenticated then let him/her enter that page else redirect to login component. If the route is public and the user is not authenticated then show the page and also show the login page if the user is not authenticated but user is authenticated and still goes to login page then redirect the user to root page. This is working fine. But if i use the render instead of component, then it does not work. I could make it work only if i pass the component from the props called component of react-router.
How can i make it work if user user render props?
Here is my code
<Switch>
<PrivateRoute
exact
path="/"
render={() => <Home name="something" />} {/* this does not work */}
/>
<PrivateRoute exact path="/demo" component={Demo} />
<PublicRoute restricted={true} path="/auth" component={Authentication} />
</Switch>
PublicRoute.js
const PublicRoute = ({component: Component, restricted, ...rest}) => {
return (
<Route
{...rest}
render={props =>
isLogin() && restricted ? <Redirect to="/" /> : <Component {...props} />
}
/>
)
}
PrivateRoute.js
const PrivateRoute = ({component: Component, ...rest}) => {
return (
<Route
{...rest}
render={props =>
isLogin() ? <Component {...props} /> : <Redirect to="/auth/login" />
}
/>
)
}
Also if there is any additional things to improve, please do suggest me.

The problem is that in your custom routes you are always using the component prop. So when passing the render prop it is overruled by the one in your custom route and thus trying to render the provided component.
When you modify it like the function below, it will work. It also extracts the render prop and if it's a function it will use that instead of the component prop.
const PrivateRoute = ({component: Component, render, ...rest}) => {
const renderContent = props => {
if (!fakeAuth.isAuthenticated) {
return (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
return (typeof render === 'function') ? render(props) : <Component {...props} />
}
return (
<Route {...rest} render={renderContent} />
);
}

Related

React: private route not navigating

I am using a private route to navigate to a route after the user has logged in. However, I am facing an issue. I don't know why but my router is not transitioning to the desired route. Here's my code:
Routes.js
...
...
<PrivateRoute
authenticated={localStorage.getItem("isAuthenticated")}
path="/dashboard"
component={DashBoard}
exact
></PrivateRoute>
PrivateRoute.js
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
<Route
{...rest}
render={props =>
authenticated ? (
<Component {...rest} {...props} />
) : (
<Redirect
to={{
pathname: '/',
state: { from: props.location }
}}
/>
)
}
/>
);
export default PrivateRoute;
Login.js
localStorage.setItem("isAuthenticated", true);
this.props.history.push('/dashboard');
Any help would be appreciated. Thanks!
So, I found the solution.
authenticated={localStorage.getItem("isAuthenticated")}
the above was invoking the method at application bootstrap due to which I was having the value of null being stored in my authenticated variable so, I changed it to arrow function and passed the argument without invoking it like below:
authenticated={() => localStorage.getItem("isAuthenticated")}
Can you just try it?
PrivateRoute.js
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => {
console.log("authenticated",authenticated)//is it true or false?
if (authenticated=="true")
return (
<Route {...rest}>
{" "}
<Component {...rest} {...props} />
</Route>
);
else
return (
<Redirect
to={{
pathname: "/",
state: { from: props.location },
}}
/>
);
};
export default PrivateRoute;

React Router Redirect not Rendering

Hei Guys,
I got a strange problem that is stopping my development progress for days now.
In my React App I'm using React-Router (Browser Router).
Everthing is working fine (Link, Route, Switch) except Redirect.
redirect is changing the URL but not Rendering the 'new' component. E.g. I got a login screen and after Submit I want to redirect to user screen. Url is changing to URL but screen stays empty (The Sidenav is rendered but not the component, that shows the users information).
Does anybody know how to solve this? There are no errors in console.
Body Component:
<Switch>
<Route exact path="/">
<Home />
</Route>
<PrivateRoute path="/user" component = {User} />
<Route path="/login">
<Login/>
</Route>
<Route path="/register">
<Register />
</Route>
<NotFound />
</Switch>
Private Route:
const PrivateRoute = ({ component: Component, user, ...rest }) => {
return (
<Route {...rest} render={
props => {
if (user) {
return <Component {...rest} {...props} />
} else {
return <Redirect to={
{
pathname: '/login',
state: {
from: props.location
}
}
} />
}
}
} />
)
}
export default PrivateRoute;
Return Value of the render method of Login Screen, this part should be rendered when logged in:
return (
<Route render={
props => {
return <Redirect to={
{
pathname: '/user',
state: {
from: this.props.location
}
}
} />
}
}
/>
);
On Reloading everthing workls perfectly.
Using of Private Route also works.
No Typos.
regards
Manuel
You should not return a Route at your render method but rather a Redirect directly.
so sth like
return (
<>
{isLoggedIn ?
<Redirect to={{
pathname: '/user',
state: { from: this.props.location }
}} />
: LOGIN_FORM_HTML
}
</>
)

react router use params returns empty object

I have a web app which is under development which is just like google drive using firebase. I have this useParams() in Dashboard Screen which is the main page of the App with All the different Folder Routes. So for this screen i have used useParams and now when i console.log(params) it shows an empty object {} and also when i click the button it does not navigate only the URL changes
Github Code :- https://github.com/KUSHAD/RDX-Drive/
In App.js
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import PrivateRoute from './Components/Route/PrivateRoute';
import Dashboard from './Screens/Main/Dashboard';
import ViewProfile from './Screens/Profile/ViewProfile';
import Signup from './Screens/Auth/Signup';
import Login from './Screens/Auth/Login';
import ForgotPassword from './Screens/Auth/ForgotPassword';
function App() {
return (
<>
<div className='App'>
<div className='main'>
<BrowserRouter>
<Switch>
{/* Drive */}
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
{/* Profile */}
<PrivateRoute path='/profile' component={ViewProfile} />
{/* Auth */}
<Route path='/signup' component={Signup} />
<Route path='/login' component={Login} />
<Route path='/forgot-password' component={ForgotPassword} />
</Switch>
</BrowserRouter>
</div>
</div>
</>
);
}
export default App;
In Dashboard.js
import NavBar from '../../Components/Shared/NavBar';
import Container from 'react-bootstrap/Container';
import AddFolderButton from '../../Components/Main/AddFolderButton';
import { useDrive } from '../../services/hooks/useDrive';
import Folder from '../../Components/Main/Folder';
import { useParams } from 'react-router-dom';
export default function Dashboard() {
const params = useParams();
console.log(params);
const { folder, childFolders } = useDrive();
return (
<div>
<NavBar />
<Container fluid>
<AddFolderButton currentFolder={folder} />
{childFolders.length > 0 && (
<div className='d-flex flex-wrap'>
{childFolders.map(childFolder => (
<div
key={childFolder.id}
className='p-2'
style={{ maxWidth: '250px' }}>
<Folder folder={childFolder} />
</div>
))}
</div>
)}
</Container>
</div>
);
}
Issue
After scouring your repo looking for the usual suspect causes for "it does not navigate only the URL changes" I didn't find anything odd like multiple Router components, etc. I think the issue is your PrivateRoute component isn't passing the props to the Route correctly. You're destructuring a prop called rest and then spread that into the Route, but you don't pass a rest prop to the PrivateRoute
export default function PrivateRoute({ component: Component, rest }) { // <-- rest prop
const { currentUser } = useAuth();
return (
<Route
{...rest} // <-- nothing is spread/passed here
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
The routes, these are not passed any prop named rest:
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
What I believe to be occurring here is the exact and path props aren't passed to the underlying Route component and so the first nested component of the Switch is matched and rendered, the "/" one that doesn't have any route params.
Solution
The fix is to spread the rest of the passed props into rest instead of destructuring a named rest prop.
export default function PrivateRoute({ component: Component, ...rest }) {
const { currentUser } = useAuth();
return (
<Route
{...rest}
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
An improvement of your private route may be as follows:
export default function PrivateRoute(props) {
const { currentUser } = useAuth();
return currentUser ? (
<Route {...props} />
) : (
<Redirect to='/login' />
);
}
This checks your user authentication and renders either a Route or Redirect. This pattern allows you to use all the regular Route props so you aren't locked into using the render prop to render the component.

Authentication for a reactjs application using two private routes

My reactjs application has two types of Users namely Artist and Lovers. Some of my components are only accessible to artist and some are only accessible to lovers. So i need to implement Artist and User Routes that will help grand access only to the required User type.
And here is my Router Switch
<Switch>
<Route exact path='/' component={Home} />
<UserRoute authed={this.state.lover} path='/user-dash' component={About} />
<ArtistRoute authed={this.state.artist} path='/artist-dash' component={Contact} />
<Route path='/SignupUser' component={SignupUser} />
</Switch>
Here is my UserRoute code
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const UserRoute = ({ component: Component, authed, ...rest }) => (
<Route {...rest} render={props => (
authed
? <Component {...props} />
: <Redirect to={{ pathname: '/', state: { from: props.location } }} />
)} />
)
I want to be able to receive the value of authed in the UserRoute passed in the switch. I do not know why authed in the UserRoute always returns false.
even when this.state.lover passed to it is true. Please what am I doing wrong.
Thanks
Route.jsx
<Switch>
<Route exact path='/' component={Home} />
<Route path='/user-dash' component={AuthCheck(About)} /> // Wrap the component with HOC
</Switch>
AuthCheck.jsx
export default function(Component) {
class AuthCheck extends Component {
render() {
if (this.props.auth.payload) {
return <Component {...this.props} /> // Component if auth is true
} else {
return <Route path='*' exact={true} component={NotFound} /> // 404 if not auth
}
}
}
function mapStateToProps(state) {
return { auth: state.auth }
}
return connect(mapStateToProps)(AuthCheck)
}
Check the above example works with redux
Make sure to import AuthCheck in the Route.jsx file

Component this.props logging undefined in console, but shows up in React Developer Tools

Private Route definition:
PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
this.state.isAuthenticated
? <Component {...props} />
: <Redirect to="/" />
)}/>
)
Passing in props in router to Home:
<this.PrivateRoute path="/home" exact component={Home} portalId={this.state.portalId} />
React developer tools shows:
Console logs:
Where is my props.portalId? I am setting the this.props.portalId after a fetch in another component so this may be the problem because of the asynchronous call? How would I fix this in that case?
Edit: Console.log is being called in Home's componentDidMount():
class Home extends Component {
componentDidMount() {
console.log(this.props.portalId);
}
To see props, you should pass props to the component rendered by <Route />, instead you pass props to the <Route />.
You can use render attribute instead of component like this:
<Route to="/anywhere" render={() => <YourComponent {...yourProps} />} />
I use once component prop:
<Route to="/anywhere" component={() => <YourComponent {...yourProps} />} />
It is worked, but recomended to use render.
It discussed sometimes on github , you can see.
Edit to working example:
PrivateRoute = ({ component: Component, ...rest }) => (
<Route render={props => (
this.state.isAuthenticated
? <Component {...props} {...rest} />
: <Redirect to="/" />
)}/>
)
I emulate your example in my env, i placed rest object in <Component /> definition, its worked for me, try it and see what happens.
It works when I do this:
PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={() => (
this.state.isAuthenticated
? <Component {...this.props}/>
: <Redirect to="/" />
)}/>
)
<this.PrivateRoute path="/home" exact component={() => <Home portalId={this.state.portalId} />} />

Categories

Resources