Skip to content
Advertisement

Rewriting React router v4 class based code to v6 functional based

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

  1. 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.

  2. 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:

  1. 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:

    • useNavigatehistory object was replaced by a navigate function.

      const navigate = useNavigate();
      
      ...
      
      navigate("....", { state: {}, replace: true });
      
    • useLocation

      const { pathname, state } = useLocation();
      
  2. 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.

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement