React-router: Difference between revisions
Line 335: | Line 335: | ||
==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="JavaScript"> | <syntaxhighlight lang="JavaScript"> | ||
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(() => { | |||
return <Redirect to= | const PerformLogout = async () => { | ||
authService.signOut(); | |||
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> | </syntaxhighlight> | ||
=Query Parameters= | =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 | 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 |
Revision as of 03:38, 7 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-cookies";
const cookies = new Cookies();
const authService = {
isAuthenticated() {
return cookies.get("auth") === "true";
},
signIn(cb) {
cookies.set("auth", true, { path: "/" });
setTimeout(cb, 100);
},
signOut(cb) {
cookies.set("auth", false, { path: "/" });
setTimeout(cb, 100);
},
};
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 () => {
authService.signOut();
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;
}