React-router: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
No edit summary
 
(43 intermediate revisions by the same user not shown)
Line 7: Line 7:
</syntaxhighlight>
</syntaxhighlight>
==Add BrowserRouter to Index.js==
==Add BrowserRouter to Index.js==
<syntaxhighlight lang="js">
<syntaxhighlight lang="jsx">
...
...
import { BrowserRouter } from 'react-router-dom';
import { BrowserRouter } from 'react-router-dom';
Line 22: Line 22:


==Add Routes to App.js==
==Add Routes to App.js==
We need to a routes to the the router we support.
We need to add routes to the the router we support. Note we can redirect from old routes within a switch (example logout below)
<syntaxhighlight lang="js">
<syntaxhighlight lang="jsx">
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';


Line 51: Line 51:
               <Route path='/messages' component={Messages} />
               <Route path='/messages' component={Messages} />
               <Route path='/profile' component={Profile} />
               <Route path='/profile' component={Profile} />
              <Route path='/auth/logout' component={Logout} />
              <Redirect to="/auth/logout" from="/auth/logOut" />
           </Switch>
           </Switch>
         </div>
         </div>
Line 61: Line 64:
</syntaxhighlight>
</syntaxhighlight>


==Create Pages==
=Router Children Component=
===Functional Component Based===
All router children components get the following props.
Create a component for each page
*match Exact Match, Params, Path and matching Url
<syntaxhighlight lang="js">
*location Key, path name and search
import React from 'react'
*history This is the browser history
=NavLink Component=
This allows us to work with the activeClassName and exact to highlight the list which is active.
<syntaxhighlight lang="jsx">
  <Router>
    <Nav authenticated={!defaultToSignIn} />
    <hr />
    <Switch>
      <Route path="/" component={Home} exact />
      <Route path="/auth/login" component={Login} />
      <Route path="/auth/Register" component={Register} />
      <Route path="/auth/logout" component={Logout} />
    </Switch>
  </Router>
</syntaxhighlight>
And for some of the NavLink routes
<syntaxhighlight lang="jsx">
            <li className="nav-item">
              <NavLink
                to="/home"
                aria-current="page"
                className="nav-link"
                exact
              >
                Home
              </NavLink>
            </li>
        </ul>
        <ul className="navbar-nav ms-auto mb-2 mb-lg-0">
            <li className="nav-item">
              <NavLink to="/explore" aria-current="page" className="nav-link">
                Explore
              </NavLink>
            </li>
            <li className="nav-item">
              <NavLink to="/messages" aria-current="page" className="nav-link">
                Messages
              </NavLink>
            </li>
</syntaxhighlight>
 
=Prompt Component=
We can prevent leaving of a page with the Prompt component. For example.
<syntaxhighlight lang="jsx" highlight="5-8">
...
  return (
    <div className="Login">
      {error && <p intent="danger">{error}</p>}
      <Prompt
        when={!usernameValid || !passwordValid}
        message="Leaving page will loose your data"
      />
      <Form onSubmit={handleSubmit} className="row g-3 needs-validation">
        <div>
          <div className="col-md-4">
            <Form.Group size="lg" controlId="username">
              <Form.Label>Username</Form.Label>
              <Form.Control
...
</syntaxhighlight>
 
=Adding a 404 Page=
To implement this we just create a route with no path and a component
<syntaxhighlight lang="jsx" highlight="23">
  <Router>
    <Nav authenticated={!defaultToSignIn} />
    <hr />
    <Switch>
      <Route
        exact
        path="/"
        render={() =>
          defaultToSignIn ? (
            <Redirect to="/auth/login" />
          ) : (
            <Redirect to="/home" />
          )
        }
      />
      <ProtectedRoute path="/" component={Home} exact />
      <ProtectedRoute path="/explore" component={Explore} />
      <ProtectedRoute path="/messages" component={Messages} />
      <ProtectedRoute path="/profile" component={Profile} />
      <Route path="/auth/login" component={Login} />
      <Route path="/auth/Register" component={Register} />
      <Route path="/auth/logout" component={Logout} />
      <Route component={PageNotFound} />
    </Switch>
  </Router>
 
</syntaxhighlight>
=Rendering Component with Parameters=
When you need to add props to the component to render we use the render property which takes a function as an argument and should return the Component for the page.
==Router==
<syntaxhighlight lang="jsx" highlight="23">
  <Router>
    <Switch>
      <Route path="/auth/logout" component={Logout} />
      <Route path="/compwithprops" render={() => {
          return <CompWithProps prop1="#ff0000" prop2='Red'/>
      }) />
      <Route component={PageNotFound} />
    </Switch>
 
</syntaxhighlight>
==Component==
For the component we need to wrap it with withRouter in a HOC.
<syntaxhighlight lang="jsx" highlight="23">
import React from "react";
 
const CompWithProps = ({prop1,prop2}) => (
  <div>
..
  </div>
);
 
CompWithProps.proptypes = {
    prop1: PropTypes.string.isRequired,
    prop2: PropTypes.string.isRequired,
}
 
export default withRouter(CompWithProps);
</syntaxhighlight>
=Custom Route with Parameters=
Here we create a custom route which takes a parameter :eid
<syntaxhighlight lang="jsx">
...
<CustomRoute path={'/api/orders/:eid'} component={Home} />
...
</syntaxhighlight>
I the custom route we now Here we can create a Route which pulls this data in the props. Like the example above we can render the component Home and the eid will be available in the props.match.params.eid
<syntaxhighlight lang="jsx">
import React from "react";
import { Route } from "react-router-dom";
 
const CustomRoute = ({ component: ComponentToRender, ...rest }) => (
  <Route
    {...rest}
    render={(props) => {
      alert(`Cusom Route ${props.match.params.eid} `);
      return <ComponentToRender {...props} />;
    }}
  />
);
export default CustomRoute;
</syntaxhighlight>
=Protected Route=
==Authentication Service==
We create an authService which emulates the following
<syntaxhighlight lang="jsx">
import Cookies from "universal-cookie";
 
const cookies = new Cookies();
const authService = {
 
  async signIn(timeout, username, password) {
    const genericErrorMessage = "Unable to perform login.";
 
    const resource = `${process.env.REACT_APP_API_ENDPOINT}v1/login`;
    const options = { timeout };
    const controller = new AbortController();
    const id = setTimeout(() => controller.abort(), timeout);
    try {
      const response = await fetch(resource, {
        options,
        signal: controller.signal,
        method: "POST",
        credentials: "include",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ username, password }),
      });
      clearTimeout(id);
      const responseAsJson = await response.json();
      if (responseAsJson.success) {
        return {
          error: false,
          status: response.status,
          token: responseAsJson.token,
        };
      }
 
      if (response.status === 400) {
        return {
          error: true,
          status: response.status,
          message: genericErrorMessage,
        };
      }
      if (response.status === 401) {
        return {
          error: true,
          status: response.status,
          message: responseAsJson.message,
        };
      }
 
      return {
        error: true,
        status: 400,
        message: responseAsJson.message,
      };
    } catch (error) {
      console.log(error);
      return {
        error: true,
        status: 500,
        message: "Unknown Error",
      };
    }
  },
 
  async signOut(token) {
    const genericErrorMessage = "Unable to perform logout.";
 
    const url = `${process.env.REACT_APP_API_ENDPOINT}v1/logout`;
 
    const response = await fetch(url, {
      method: "GET",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`,
      },
    });
 
    const responseAsJson = await response.json();
    if (!responseAsJson.success) {
      if (response.status === 400) {
        return {
          error: true,
          status: response.status,
          message: genericErrorMessage,
        };
      }
      if (response.status === 401) {
        return {
          error: true,
          status: response.status,
          message: responseAsJson.message,
        };
      }
      return {
        error: true,
        status: 400,
        message: responseAsJson.message,
      };
    }
    return {
      error: false,
      status: response.status,
    };
  },
};
 
export default authService;
 
</syntaxhighlight>
 
==Create Private Route==
===Introduction===
This is more complicated than expected. What we are trying to achieve is
*If unprotected just route
*If not authenticated and route protected, prompt to login, and route
*if authenticated route to protected route
We need to make sure that people who access /user/myprofile get prompted to sign in and then if successful proceed to /user/myprofile. To achieve this we need to
*Change routes to use custom route
*Create a custom Route to check for authentication
*Store the original route in the Login so after login continue
===Change routes to use custom route===
We will make a custom route PrivateRoute which will perform checks on the protected routes.
<syntaxhighlight lang="jsx">
  <Router>
    <Switch>
      <PrivateRoute path="/" component={Home} exact />
      <PrivateRoute path="/explore" component={Explore} />
      <Privateoute path="/messages" component={Messages} />
      <PrivateRoute path="/profile" component={Profile} />
      <Route path="/auth/login" component={Login} />
      <Route path="/auth/Register" component={Register} />
      <Route path="/auth/logout" component={Logout} />
      <Route component={PageNotFound} />
    </Switch>
  </Router>
</syntaxhighlight>
===Create a custom Route to check for authentication===
We wrap the component to render which allows us to do some processing. In this example if authenticated, we render the component or we redirect to the /login route. Before doing so store the requested protected route.
<syntaxhighlight lang="jsx" highlight="12-15">
import React from "react";
import { Redirect } from "react-router-dom";
import authService from "../service/authService";
 
const PrivateRoute = ({ component: Component, ...rest }) => (
  <Route {...rest}
    render={(props) => {
      authService.isAuthenticated() ? (
        <Component {...props} />
      ) : (
          <Redirect to={{
              pathname: '/login',
              state: { target: props.location }   
          }} />
      );
    }}
  />
);
 
export default PrivateRoute;
</syntaxhighlight>
===Store the original route in the Login so after login continue===
Here we process the form but the main processing is highlighted.<br>
<br>
We extract the state if set in the ProtectedRoute above. If it wasn't we default to the login, if it was we use that route.<br>
<br>
If we are authenticated we either go to the default route or the route set in the ProtectedRoute.
<syntaxhighlight lang="jsx">
...
import { UserContext } from "../context/UserContext";


const Home = () => {
const Login = (props) => {
...
  useEffect(() => {
    const updateFormValid = () => {
      if (usernameValid === isValid && passwordValid === isValid) {
        setFormValid(true);
      } else {
        setFormValid(false);
      }
    };
    updateFormValid();
  }, [usernameValid, passwordValid]);
 
  const { target } = props.location.state || {
    target: { pathname: "/auth/login" },
  };
 
  if (userContext.token) {
    if (target.pathname === "/auth/login") {
      target.pathname = "/";
    }
    return <Redirect to={target} />;
  }
...
  const handleSubmit = async (e) => {
    e.preventDefault();
...
  };
   return (
   return (
    <div>
...
        <h2>Home</h2>
    </div>
   );
   );
};
};
export default Home
 
export default Login;
</syntaxhighlight>
</syntaxhighlight>
===Class Component Based===
Create a component for each page
<syntaxhighlight lang="js">
import React, { Component } from 'react';


class Profile extends Component {
==Logout==
   render() {
Here is the example of the logout function. The main issue I came across was the rendering occurred twice which I was let to believe because of React.StrictMode. To overcome this I used the useEffect hook.
     return (
<syntaxhighlight lang="jsx">
         <div>
import React, { useEffect, useState, useContext } from "react";
           <h2>Profile</h2>
import { Redirect } from "react-router-dom";
        </div>
import { UserContext } from "../context/UserContext";
    );
import authService from "../service/authService";
  }
 
}
const Logout = () => {
export default Profile;
   const [userContext, setUserContext] = useContext(UserContext);
  const [, setError] = useState();
 
  const genericErrorMessage = "Something went wrong! Please try again later.";
 
  useEffect(() => {
     const PerformLogout = async () => {
 
      const url = `${process.env.REACT_APP_API_ENDPOINT}v1/logout`;
      const response = await fetch(url, {
        method: "GET",
        credentials: "include",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${userContext.token}`,
        },
      });
 
      const responseAsJson = await response.json();
 
      if (!responseAsJson.success) {
        if (response.status === 400) {
          setError(genericErrorMessage);
        } else if (response.status === 401) {
          setError(responseAsJson.message);
        } else {
          setError(genericErrorMessage);
         }
      } else {
        setUserContext((oldValues) => ({
           ...oldValues,
          details: undefined,
          token: null,
        }));
      }
    };
 
    PerformLogout();
 
    // Anything in here is fired on component mount.
  }, []);
 
  return <Redirect to="/auth/login" />;
};
 
export default Logout;
</syntaxhighlight>
 
=Query Parameters=
We can extract the query parameters in React with. The URLSearchParams code below is not well supported instead we need to use the npm package querystring
<syntaxhighlight lang="JavaScript">
// const query = new URLSearchParams(props.location.search);
// const name = query.get('name')
// const occupation = query.get('occupation')
 
import * as querystring from "query-string";
const qsValues = querystring.parse(props.location.search);
const name = qsValues.name;
const occupation = qsValues.occupation
 
</syntaxhighlight>
=Routing and Redux=
This did seem popular on the web so
==Reminder of Redux==
Here is a quick reminder of redux.
* Views subscribe to changes in state in the store
* State changes are passed to views as properties
* Components dispatch action requests to action creators
* Action creators return an action object
* Actions are payloads sent to the reducer
* The reducer is responsible for making the change in the store
* The state is replaced as it is immutable
[[File:Redux reminder.png|600px]]
<br>
To get the router to be used in redux was fairly straight forward
<syntaxhighlight lang="jsx">
import {ConnectdRouter as Router, routerMiddleware} from "react-router-redux"
...
import createHistory from "history/createBrowserHistory"
import initialState from "./data/blahblah"
 
const history = createHistory()
const middleware = routerMiddleware(history)
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
 
...
<Provider store={store}>
    <Router history={history}>
        <App store={store} />
    </Router>
</Provide>
</syntaxhighlight>
We need to add the route to the reducers to.
<syntaxhighlight lang="jsx">
...
const rootReducer = combineReducers({
    app: appReducer,
    router: routeReducer,
})
...
</syntaxhighlight>
</syntaxhighlight>
===Component Based===


= Transitions =
=Transitions=
You need to provide a class prefix in this case trans.
You need to provide a class prefix in this case trans.
<syntaxhighlight lang="JavaScript">
<syntaxhighlight lang="jsx">
import {TransitionGroup, CSSTransition} from 'react-transition-group'
import {TransitionGroup, CSSTransition} from 'react-transition-group'



Latest revision as of 01:20, 12 July 2021

Introduction

This page is about routering with React.

Setup

Install package for Router

npm i react-router-dom

Add BrowserRouter to Index.js

...
import { BrowserRouter } from 'react-router-dom';
...
ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);

Add Routes to App.js

We need to add routes to the the router we support. Note we can redirect from old routes within a switch (example logout below)

import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';

import Explore from './components/Explore';
import Home from './components/Home';
import Messages from './components/Messages';
import Profile from './components/Profile';

function App() {
  return (

    <Router>
        <div>
          <h2>Welcome to React Router Tutorial</h2>
          <nav className="navbar navbar-expand-lg navbar-light bg-light">
          <ul className="navbar-nav mr-auto">
            <li><Link to={'/'} className="nav-link"> Home </Link></li>
            <li><Link to={'/explore'} className="nav-link">Explore</Link></li>
            <li><Link to={'/messages'} className="nav-link">Messages</Link></li>
            <li><Link to={'/profile'} className="nav-link">Profile</Link></li>
          </ul>
          </nav>
          <hr />
          <Switch>
              <Route exact path='/' component={Home} />
              <Route path='/explore' component={Explore} />
              <Route path='/messages' component={Messages} />
              <Route path='/profile' component={Profile} />
              <Route path='/auth/logout' component={Logout} />
              <Redirect to="/auth/logout" from="/auth/logOut" />
 
          </Switch>
        </div>
      </Router>

  );
}

export default App;

Router Children Component

All router children components get the following props.

  • match Exact Match, Params, Path and matching Url
  • location Key, path name and search
  • history This is the browser history

NavLink Component

This allows us to work with the activeClassName and exact to highlight the list which is active.

  <Router>
    <Nav authenticated={!defaultToSignIn} />
    <hr />
    <Switch>
      <Route path="/" component={Home} exact />
      <Route path="/auth/login" component={Login} />
      <Route path="/auth/Register" component={Register} />
      <Route path="/auth/logout" component={Logout} />
    </Switch>
  </Router>

And for some of the NavLink routes

            <li className="nav-item">
              <NavLink
                to="/home"
                aria-current="page"
                className="nav-link"
                exact
              >
                Home
              </NavLink>
            </li>
        </ul>
        <ul className="navbar-nav ms-auto mb-2 mb-lg-0">
            <li className="nav-item">
              <NavLink to="/explore" aria-current="page" className="nav-link">
                Explore
              </NavLink>
            </li>
            <li className="nav-item">
              <NavLink to="/messages" aria-current="page" className="nav-link">
                Messages
              </NavLink>
            </li>

Prompt Component

We can prevent leaving of a page with the Prompt component. For example.

...
  return (
    <div className="Login">
      {error && <p intent="danger">{error}</p>}
      <Prompt
        when={!usernameValid || !passwordValid}
        message="Leaving page will loose your data"
      />
      <Form onSubmit={handleSubmit} className="row g-3 needs-validation">
        <div>
          <div className="col-md-4">
            <Form.Group size="lg" controlId="username">
              <Form.Label>Username</Form.Label>
              <Form.Control
...

Adding a 404 Page

To implement this we just create a route with no path and a component

  <Router>
    <Nav authenticated={!defaultToSignIn} />
    <hr />
    <Switch>
      <Route
        exact
        path="/"
        render={() =>
          defaultToSignIn ? (
            <Redirect to="/auth/login" />
          ) : (
            <Redirect to="/home" />
          )
        }
      />
      <ProtectedRoute path="/" component={Home} exact />
      <ProtectedRoute path="/explore" component={Explore} />
      <ProtectedRoute path="/messages" component={Messages} />
      <ProtectedRoute path="/profile" component={Profile} />
      <Route path="/auth/login" component={Login} />
      <Route path="/auth/Register" component={Register} />
      <Route path="/auth/logout" component={Logout} />
      <Route component={PageNotFound} />
    </Switch>
  </Router>

Rendering Component with Parameters

When you need to add props to the component to render we use the render property which takes a function as an argument and should return the Component for the page.

Router

  <Router>
    <Switch>
      <Route path="/auth/logout" component={Logout} />
      <Route path="/compwithprops" render={() => {
           return <CompWithProps prop1="#ff0000" prop2='Red'/>
      }) />
      <Route component={PageNotFound} />
    </Switch>

Component

For the component we need to wrap it with withRouter in a HOC.

import React from "react";

const CompWithProps = ({prop1,prop2}) => (
  <div>
..
  </div>
);

CompWithProps.proptypes = {
    prop1: PropTypes.string.isRequired,
    prop2: PropTypes.string.isRequired,
}

export default withRouter(CompWithProps);

Custom Route with Parameters

Here we create a custom route which takes a parameter :eid

...
<CustomRoute path={'/api/orders/:eid'} component={Home} />
...

I the custom route we now Here we can create a Route which pulls this data in the props. Like the example above we can render the component Home and the eid will be available in the props.match.params.eid

import React from "react";
import { Route } from "react-router-dom";

const CustomRoute = ({ component: ComponentToRender, ...rest }) => (
  <Route
    {...rest}
    render={(props) => {
      alert(`Cusom Route ${props.match.params.eid} `);
      return <ComponentToRender {...props} />;
    }}
  />
);
export default CustomRoute;

Protected Route

Authentication Service

We create an authService which emulates the following

import Cookies from "universal-cookie";

const cookies = new Cookies();
const authService = {

  async signIn(timeout, username, password) {
    const genericErrorMessage = "Unable to perform login.";

    const resource = `${process.env.REACT_APP_API_ENDPOINT}v1/login`;
    const options = { timeout };
    const controller = new AbortController();
    const id = setTimeout(() => controller.abort(), timeout);
    try {
      const response = await fetch(resource, {
        options,
        signal: controller.signal,
        method: "POST",
        credentials: "include",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ username, password }),
      });
      clearTimeout(id);
      const responseAsJson = await response.json();
      if (responseAsJson.success) {
        return {
          error: false,
          status: response.status,
          token: responseAsJson.token,
        };
      }

      if (response.status === 400) {
        return {
          error: true,
          status: response.status,
          message: genericErrorMessage,
        };
      }
      if (response.status === 401) {
        return {
          error: true,
          status: response.status,
          message: responseAsJson.message,
        };
      }

      return {
        error: true,
        status: 400,
        message: responseAsJson.message,
      };
    } catch (error) {
      console.log(error);
      return {
        error: true,
        status: 500,
        message: "Unknown Error",
      };
    }
  },

  async signOut(token) {
    const genericErrorMessage = "Unable to perform logout.";

    const url = `${process.env.REACT_APP_API_ENDPOINT}v1/logout`;

    const response = await fetch(url, {
      method: "GET",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`,
      },
    });

    const responseAsJson = await response.json();
    if (!responseAsJson.success) {
      if (response.status === 400) {
        return {
          error: true,
          status: response.status,
          message: genericErrorMessage,
        };
      }
      if (response.status === 401) {
        return {
          error: true,
          status: response.status,
          message: responseAsJson.message,
        };
      }
      return {
        error: true,
        status: 400,
        message: responseAsJson.message,
      };
    }
    return {
      error: false,
      status: response.status,
    };
  },
};

export default authService;

Create Private Route

Introduction

This is more complicated than expected. What we are trying to achieve is

  • If unprotected just route
  • If not authenticated and route protected, prompt to login, and route
  • if authenticated route to protected route

We need to make sure that people who access /user/myprofile get prompted to sign in and then if successful proceed to /user/myprofile. To achieve this we need to

  • Change routes to use custom route
  • Create a custom Route to check for authentication
  • Store the original route in the Login so after login continue

Change routes to use custom route

We will make a custom route PrivateRoute which will perform checks on the protected routes.

  <Router>
    <Switch>
      <PrivateRoute path="/" component={Home} exact />
      <PrivateRoute path="/explore" component={Explore} />
      <Privateoute path="/messages" component={Messages} />
      <PrivateRoute path="/profile" component={Profile} />
      <Route path="/auth/login" component={Login} />
      <Route path="/auth/Register" component={Register} />
      <Route path="/auth/logout" component={Logout} />
      <Route component={PageNotFound} />
    </Switch>
  </Router>

Create a custom Route to check for authentication

We wrap the component to render which allows us to do some processing. In this example if authenticated, we render the component or we redirect to the /login route. Before doing so store the requested protected route.

import React from "react";
import { Redirect } from "react-router-dom";
import authService from "../service/authService";

const PrivateRoute = ({ component: Component, ...rest }) => (
  <Route {...rest}
    render={(props) => {
      authService.isAuthenticated() ? (
        <Component {...props} />
      ) : (
          <Redirect to={{
              pathname: '/login',
              state: { target: props.location }    
          }} />
      );
    }}
  />
);

export default PrivateRoute;

Store the original route in the Login so after login continue

Here we process the form but the main processing is highlighted.

We extract the state if set in the ProtectedRoute above. If it wasn't we default to the login, if it was we use that route.

If we are authenticated we either go to the default route or the route set in the ProtectedRoute.

...
import { UserContext } from "../context/UserContext";

const Login = (props) => {
...
  useEffect(() => {
    const updateFormValid = () => {
      if (usernameValid === isValid && passwordValid === isValid) {
        setFormValid(true);
      } else {
        setFormValid(false);
      }
    };
    updateFormValid();
  }, [usernameValid, passwordValid]);

  const { target } = props.location.state || {
    target: { pathname: "/auth/login" },
  };

  if (userContext.token) {
    if (target.pathname === "/auth/login") {
      target.pathname = "/";
    }
    return <Redirect to={target} />;
  }
...
  const handleSubmit = async (e) => {
    e.preventDefault();
...
  };
  return (
...
  );
};

export default Login;

Logout

Here is the example of the logout function. The main issue I came across was the rendering occurred twice which I was let to believe because of React.StrictMode. To overcome this I used the useEffect hook.

import React, { useEffect, useState, useContext } from "react";
import { Redirect } from "react-router-dom";
import { UserContext } from "../context/UserContext";
import authService from "../service/authService";

const Logout = () => {
  const [userContext, setUserContext] = useContext(UserContext);
  const [, setError] = useState();

  const genericErrorMessage = "Something went wrong! Please try again later.";

  useEffect(() => {
    const PerformLogout = async () => {

      const url = `${process.env.REACT_APP_API_ENDPOINT}v1/logout`;
      const response = await fetch(url, {
        method: "GET",
        credentials: "include",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${userContext.token}`,
        },
      });

      const responseAsJson = await response.json();

      if (!responseAsJson.success) {
        if (response.status === 400) {
          setError(genericErrorMessage);
        } else if (response.status === 401) {
          setError(responseAsJson.message);
        } else {
          setError(genericErrorMessage);
        }
      } else {
        setUserContext((oldValues) => ({
          ...oldValues,
          details: undefined,
          token: null,
        }));
      }
    };

    PerformLogout();

    // Anything in here is fired on component mount.
  }, []);

  return <Redirect to="/auth/login" />;
};

export default Logout;

Query Parameters

We can extract the query parameters in React with. The URLSearchParams code below is not well supported instead we need to use the npm package querystring

// const query = new URLSearchParams(props.location.search);
// const name = query.get('name')
// const occupation = query.get('occupation')

import * as querystring from "query-string";
const qsValues = querystring.parse(props.location.search);
const name = qsValues.name;
const occupation = qsValues.occupation

Routing and Redux

This did seem popular on the web so

Reminder of Redux

Here is a quick reminder of redux.

  • Views subscribe to changes in state in the store
  • State changes are passed to views as properties
  • Components dispatch action requests to action creators
  • Action creators return an action object
  • Actions are payloads sent to the reducer
  • The reducer is responsible for making the change in the store
  • The state is replaced as it is immutable


To get the router to be used in redux was fairly straight forward

import {ConnectdRouter as Router, routerMiddleware} from "react-router-redux"
...
import createHistory from "history/createBrowserHistory"
import initialState from "./data/blahblah"

const history = createHistory()
const middleware = routerMiddleware(history)
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

...
<Provider store={store}>
    <Router history={history}>
        <App store={store} />
    </Router>
</Provide>

We need to add the route to the reducers to.

...
const rootReducer = combineReducers({
    app: appReducer,
    router: routeReducer,
})
...

Transitions

You need to provide a class prefix in this case trans.

import {TransitionGroup, CSSTransition} from 'react-transition-group'

<TransactionGroup>
  <CSSTransition key={location.key} classNames={'trans'} timeout={1000}>
    <Switch location={location}>
     <Route path={`${match.url}`} component={LoremNumber} exact/>
     <Route path={`${match.url}/:id`} component={LoremNumber} exact/>
    </Switch>
</TransactionGroup>

And the CSS

.trans-enter {
   opacity: 0;
   z-index: 1;
}

.trans-exit {
   display: none
}

.trans-enter.trans-enter-active {
   opacity: 1;
   transition: opacity 1000ms ease-in;
}