Rewriting React router v4 class based code to v6 functional based - javascript

I'm trying to implement oauh login with react and spring boot and I've found a tutorial I can follow.
The issue I have is that it is using React Router v4, I would like to update it to use React Router v6 and using Functional components instead.
Login.js
import React, { Component } from 'react';
import './Login.css';
import { GOOGLE_AUTH_URL, FACEBOOK_AUTH_URL, GITHUB_AUTH_URL, ACCESS_TOKEN } from '../../constants';
import { login } from '../../util/APIUtils';
import { Link, Redirect } from 'react-router-dom'
import fbLogo from '../../img/fb-logo.png';
import googleLogo from '../../img/google-logo.png';
import githubLogo from '../../img/github-logo.png';
import Alert from 'react-s-alert';
class Login extends Component {
componentDidMount() {
// If the OAuth2 login encounters an error, the user is redirected to the /login page with an error.
// Here we display the error and then remove the error query parameter from the location.
if(this.props.location.state && this.props.location.state.error) {
setTimeout(() => {
Alert.error(this.props.location.state.error, {
timeout: 5000
});
this.props.history.replace({
pathname: this.props.location.pathname,
state: {}
});
}, 100);
}
}
render() {
if(this.props.authenticated) {
return <Redirect
to={{
pathname: "/",
state: { from: this.props.location }
}}/>;
}
return (
<div className="login-container">
<div className="login-content">
<h1 className="login-title">Login to SpringSocial</h1>
<SocialLogin />
<div className="or-separator">
<span className="or-text">OR</span>
</div>
<LoginForm {...this.props} />
<span className="signup-link">New user? <Link to="/signup">Sign up!</Link></span>
</div>
</div>
);
}
}
class SocialLogin extends Component {
render() {
return (
<div className="social-login">
<a className="btn btn-block social-btn google" href={GOOGLE_AUTH_URL}>
<img src={googleLogo} alt="Google" /> Log in with Google</a>
<a className="btn btn-block social-btn facebook" href={FACEBOOK_AUTH_URL}>
<img src={fbLogo} alt="Facebook" /> Log in with Facebook</a>
<a className="btn btn-block social-btn github" href={GITHUB_AUTH_URL}>
<img src={githubLogo} alt="Github" /> Log in with Github</a>
</div>
);
}
}
App.js
This is the App.js with the routes, I have updated it to use Functional components and React Router v6.
//imports left out
function App() {
const [globalUserState, setGlobalUserState] = useState({
authenticated: false,
currentUser: null,
loading: true
});
useEffect(() => {
loadCurrentlyLoggedInUser();
})
const loadCurrentlyLoggedInUser = () => {
getCurrentUser()
.then(res => {
setGlobalUserState({
currentUser: res,
authenticated: true,
loading: false
});
}).catch(err => {
setGlobalUserState({
loading: false
})
})
}
const handleLogout = () => {
localStorage.removeItem(ACCESS_TOKEN);
setGlobalUserState({
authenticated: false,
currentUser: null
});
Alert.success("You're safely logged out!");
}
return (
<Router>
<div className="app">
<div className="app-header">
<AppHeader />
</div>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<SecuredRoute> <Profile /> </SecuredRoute>} />
<Route path="/login" element={(props) => <Login authenticated={globalUserState.authenticated} {...props} />} />
<Route path="/signup" element={(props) => <Signup authenticated={globalUserState.authenticated} {...props} />} />
<Route path="/oauth2/redirect" element={<OAuth2RedirectHandler />} />
<Route path="*" element={<Notfound />} />
</Routes>
<Alert stack={{limit: 3}}
timeout = {3000}
position='top-right' effect='slide' offset={65}
/>
</div>
</Router>
);
}
export default App;
What I would like clarity on
I'm struggling to understand the equivalent of the react router functionalities with v6 (location.state.error, history.replace, location.pathname etc) and functional components instead of class based.
Also, If someone can explain this line please
<LoginForm {...this.props} />

Q1
I'm struggling to understand the equivalent of the react router
functionalities with v6 (location.state.error, history.replace,
location.pathname etc) and functional components instead of class
based.
In react-router-dom v6 there are no longer route props, i.e. no history, location, and no match. The Route components also no longer have component or render props that take a reference to a React component or a function that returns JSX, instead they were replaced by the element prop that takes a JSX literal, i.e. ReactElement.
If I'm understanding your question(s) correctly you are asking how to use RRDv6 with the class components Login and Signup.
You've a couple options:
Convert Login and Signup into React function components as well and use the new React hooks.
I won't cover the conversion, but the hooks to use are:
useNavigate - history object was replaced by a navigate function.
const navigate = useNavigate();
...
navigate("....", { state: {}, replace: true });
useLocation
const { pathname, state } = useLocation();
Create a custom withRouter component that can use the hooks and pass them down as props.
const withRouter = WrappedComponent => props => {
const navigate = useNavigate();
const location = useLocation();
// etc... other react-router-dom v6 hooks
return (
<WrappedComponent
{...props}
navigate={navigate}
location={location}
// etc...
/>
);
};
Decorate the Login and Signup exports:
export default withRouter(Login);
Swap from this.props.history.push to this.props.navigate:
componentDidMount() {
// If the OAuth2 login encounters an error, the user is redirected to the /login page with an error.
// Here we display the error and then remove the error query parameter from the location.
if (this.props.location.state && this.props.location.state.error) {
setTimeout(() => {
const { pathname, state } = this.props.location;
Alert.error(state.error, { timeout: 5000 });
this.props.navigate(
pathname,
{ state: {}, replace: true }
);
}, 100);
}
}
What remains is to fix the routes in App so they are correctly rendering JSX.
<Router>
<div className="app">
<div className="app-header">
<AppHeader />
</div>
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/profile"
element={(
<SecuredRoute>
<Profile />
</SecuredRoute>
)}
/>
<Route
path="/login"
element={<Login authenticated={globalUserState.authenticated} />}
/>
<Route
path="/signup"
element={<Signup authenticated={globalUserState.authenticated} />}
/>
<Route path="/oauth2/redirect" element={<OAuth2RedirectHandler />} />
<Route path="*" element={<Notfound />} />
</Routes>
<Alert stack={{limit: 3}}
timeout = {3000}
position='top-right' effect='slide' offset={65}
/>
</div>
</Router>
Q2
Also, If someone can explain this line please <LoginForm {...this.props} />
This is simply taking all the props that were passed to the parent component and copying/passing along to the LoginForm component.
<LoginForm {...this.props} />
Login is passed an authenticated prop as well as whatever new "route props" were injected, and any other props injected by any other HOCs you may be using, and the above passes them all along to LoginForm.

Related

React V6 having issues navigating from Class component [duplicate]

I started learning react about 15 days back. The following code adds the post correctly but does not redirect to "/". I am using react-router-dom v6.
render(){
return <div>
<Routes>
<Route exact path="/" element={
<div>
<Title title={'Arijit - Photowall'}/>
<Photowall posts={this.state.posts} onRemovePhoto={this.removePhoto} />
</div>
} >
</Route>
<Route path="/addPhotos" element={
<AddPhoto onAddPhoto={(addedPost)=>{
this.addPhoto(addedPost)
}}
>
<Navigate to="/" />
</AddPhoto>
}/>
</Routes>
</div>
}
In react-router-dom#6 the way to issue imperative navigation actions is to use the navigate function returned from the useNavigate hook. The code you've shared in the snippet is from a class component though, so you'll need to create a Higher Order Component to use the useNavigate hook and inject the navigate function as a prop.
Example:
import { useNavigate } from 'react-router-dom';
const withNavigate = Component => props => {
const navigate = useNavigate();
return <Component {...props} navigate={navigate} />;
};
Decorate the component in your snippet with this withNavigate HOC.
export withNavigate(MyComponent);
Access the navigate function from props.
render(){
const { navigate } = this.props;
return (
<div>
<Routes>
<Route
path="/"
element={(
<div>
<Title title={'Arijit - Photowall'}/>
<Photowall posts={this.state.posts} onRemovePhoto={this.removePhoto} />
</div>
)}
/>
<Route
path="/addPhotos"
element={(
<AddPhoto
onAddPhoto={(addedPost) => {
this.addPhoto(addedPost);
navigate("/");
}}
/>
)}
/>
</Routes>
</div>
);
}
Using Typescript
interface WithRouter {
location: ReturnType<typeof useLocation>;
navigate: ReturnType<typeof useNavigate>;
params: ReturnType<typeof useParams>;
}
const withRouter = <P extends {}>(Component: React.ComponentType<P>) => (
props: Omit<P, keyof WithRouter>
) => {
const location = useLocation();
const navigate = useNavigate();
const params = useParams();
return <Component {...(props as P)} {...{ location, navigate, params }} />;
};
Example Usage:
interface MyComponentProps {
foo: string;
}
type MyComponentPropsWithRouter = MyComponentProps & WithRouter
class MyComponent extends React.Component<MyComponentPropsWithRouter> {
render() {
const { foo, navigate, location, params } = this.props;
const { bar } = params as { bar?: string };
return (
<>
<h1>MyComponent: {location.pathname}</h1>
<h2>Foo prop: {foo}</h2>
<h2>Param?: {bar}</h2>
<button type="button" onClick={() => navigate("/test")}>
Navigate
</button>
</>
);
}
}
const MyDecoratedComponent = withRouter(MyComponent);
Routes must be contained into a Router (Usually) BrowserRouter, so, you should put them all inside of that component, something like this:
<BrowserRouter>
<div className="App">
<Box data-testid="app-container">
<Routes>
<Route path={"/"} element={<Home />} />
<Route path={"/edit"} element={<edit/>} />
<Route path={"/whatever"} element={<whatever/>} />
</Routes>
</Box>
</div>
</BrowserRouter>
regarding to the navigate, in react-router-dom v6 you must use the hook useNavigate() and it works as:
const navigate = useNavigate();
<Button
text={ES.common.back}
onClick={() => navigate("/")}
></Button>
You'll have to import
import { useNavigate } from "react-router-dom";
Here's some documentation that you may find it helpful

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.

Pass Props in a Private Route React

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} />
)
}
/>
)

React: "history" prop is not available in a private route

I'm using React 16, React-router-dom 4 and Mobx in my app.
I have this code for a private route:
export default (props) => {
console.log('props from private',props)//Here i can see that the component doesn't contain the "history" prop.
const Component = props.component;
const match = props.computedMatch
if (isValidated()) {
return (
<div>
<div><Component {...props} match={match} /></div>
</div>
)
} else {
return <Redirect to="/login" />
}
};
This is the routing setup:
export const history = createHistory();
const AppRouter = () => (
<Router history={history}>
<Switch>
<PrivateRoute path="/" component={Chat} exact={true} />
<Route path="/login" component={Login} />
</Switch>
</Router>
);
For some reason, the history prop just doesn't exist in the private route, therefore i'm unable to use the this.props.history.push function, to redirect programatically. The prop does get passed to a "normal" route though.
What is wrong with my code?
Use below:
import {withRouter} from 'react-router-dom';
wrap component with withRouter.
withRouter(component_name)

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

Categories

Resources