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} />
Advertisement
Answer
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
andSignup
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 anavigate
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
andSignup
exports:export default withRouter(Login);
Swap from
this.props.history.push
tothis.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
.