React-router: Difference between revisions
No edit summary |
|||
(21 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=" | <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 | 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=" | <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 60: | Line 63: | ||
export default App; | export default App; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=Router Children Component= | =Router Children Component= | ||
Line 102: | Line 71: | ||
=NavLink Component= | =NavLink Component= | ||
This allows us to work with the activeClassName and exact to highlight the list which is active. | This allows us to work with the activeClassName and exact to highlight the list which is active. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
<Router> | <Router> | ||
<Nav authenticated={!defaultToSignIn} /> | <Nav authenticated={!defaultToSignIn} /> | ||
Line 115: | Line 84: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
And for some of the NavLink routes | And for some of the NavLink routes | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
<li className="nav-item"> | <li className="nav-item"> | ||
<NavLink | <NavLink | ||
Line 142: | Line 111: | ||
=Prompt Component= | =Prompt Component= | ||
We can prevent leaving of a page with the Prompt component. For example. | We can prevent leaving of a page with the Prompt component. For example. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx" highlight="5-8"> | ||
... | ... | ||
return ( | return ( | ||
Line 159: | Line 128: | ||
... | ... | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=Adding a 404 Page= | =Adding a 404 Page= | ||
To implement this we just create a route with no path and a component | To implement this we just create a route with no path and a component | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx" highlight="23"> | ||
<Router> | <Router> | ||
<Nav authenticated={!defaultToSignIn} /> | <Nav authenticated={!defaultToSignIn} /> | ||
Line 192: | Line 162: | ||
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. | 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== | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx" highlight="23"> | ||
<Router> | <Router> | ||
<Switch> | <Switch> | ||
Line 205: | Line 175: | ||
==Component== | ==Component== | ||
For the component we need to wrap it with withRouter in a HOC. | For the component we need to wrap it with withRouter in a HOC. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx" highlight="23"> | ||
import React from "react"; | import React from "react"; | ||
Line 223: | Line 193: | ||
=Custom Route with Parameters= | =Custom Route with Parameters= | ||
Here we create a custom route which takes a parameter :eid | Here we create a custom route which takes a parameter :eid | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
... | ... | ||
<CustomRoute path={'/api/orders/:eid'} component={Home} /> | <CustomRoute path={'/api/orders/:eid'} component={Home} /> | ||
Line 229: | Line 199: | ||
</syntaxhighlight> | </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 | 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=" | <syntaxhighlight lang="jsx"> | ||
import React from "react"; | import React from "react"; | ||
import { Route } from "react-router-dom"; | import { Route } from "react-router-dom"; | ||
Line 247: | Line 217: | ||
==Authentication Service== | ==Authentication Service== | ||
We create an authService which emulates the following | We create an authService which emulates the following | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
import Cookies from "universal- | import Cookies from "universal-cookie"; | ||
const cookies = new Cookies(); | const cookies = new Cookies(); | ||
const authService = { | 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; | export default authService; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==Create Private Route== | ==Create Private Route== | ||
We | ===Introduction=== | ||
<syntaxhighlight lang=" | 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 React from "react"; | ||
import { Redirect } from "react-router-dom"; | import { Redirect } from "react-router-dom"; | ||
Line 282: | Line 365: | ||
<Component {...props} /> | <Component {...props} /> | ||
) : ( | ) : ( | ||
<Redirect to={{ | |||
<Redirect to={ | |||
pathname: '/login', | pathname: '/login', | ||
state: { target: props.location } | state: { target: props.location } | ||
} /> | }} /> | ||
); | ); | ||
}} | }} | ||
Line 294: | Line 376: | ||
export default PrivateRoute; | export default PrivateRoute; | ||
</syntaxhighlight> | </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 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; | |||
</syntaxhighlight> | </syntaxhighlight> | ||
==Logout== | ==Logout== | ||
Here is the example of the logout function. | 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. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
import React, { useEffect, useState, useContext } from "react"; | |||
import { Redirect } from "react-router-dom"; | import { Redirect } from "react-router-dom"; | ||
import { UserContext } from "../context/UserContext"; | |||
import authService from "../service/authService"; | 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; | 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> | ||
=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=" | <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
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;
}