Reputation: 157
I'm trying to implement a simple authorization flow using React Router 4 + Redux.
authorization
(boolean in the store) is false
, any route should to redirect to the login page. authorization
is true
, any route should render that route. <CheckAuth>
that I placed in a container (ie react-redux connect), for access to the authorization
boolean in the store.<CheckAuth>
accepts two components, <Protected>
and <LoginPage>
, and decides between one or the other based on the boolean.<CheckAuth>
, either a <Route>
to the requested component or a <Redirect>
is rendered, depending on authorization
.<LoginPage>
will dispatch a LOGIN
action that sets the authorization
boolean to true.<Protected>
will dispatch a LOGOUT
action that sets the authorization
boolean to false. (This form should be separate from <Protected>
, but I put them together to keep the example simple.)The problem I'm running into is that components don't seem to be updating when the store changes. Hard-refreshing the page updates the view, but it also wipes out the store, as well as just being buggy.
I'm probably making some false assumptions somewhere. If anyone can help, I would appreciate it!
I've created a simplified version of my code, shown below, which can be pasted into a create-react-app
App.js
file. Below that is a package.json
file to get it up and running. (In the version I'm working on, the auth is persisted to a cookie, so refreshes don't clear auth.)
In addition to any solutions that might be available, I'd love any tips on debugging a react app, particularly wrt the router.
App.js:
// import Libs
import React, { Component } from "react"
import { BrowserRouter as Router, Route, Redirect } from "react-router-dom"
import { createStore, applyMiddleware } from "redux"
import { Provider, connect } from "react-redux"
import { createLogger } from "redux-logger"
const loggerMiddleware = createLogger()
// create Store/Reducer
const store = createStore((state = { authenticated: false }, action) => {
switch (action.type) {
case "LOGIN":
return {
authenticated: true
}
case "LOGOUT":
return {
authenticated: false
}
default:
return state
}
}, applyMiddleware(loggerMiddleware))
// <CheckAuth> component
const CheckAuth = ({ authenticated, location, LoginPage, Protected }) => {
const { pathname } = location
if (authenticated) {
if (pathname === "/login") {
return <Redirect to="/" />
}
return <Route path={pathname} component={Protected} />
}
if (pathname === "/login") {
return <Route path="/login" component={LoginPage} />
}
return <Redirect to="login" />
}
const mapStateToProps = state => ({
authenticated: state.authenticated
})
const CheckAuthContainer = connect(mapStateToProps)(CheckAuth)
// Log in and out Actions
const authenticate = () => ({
type: "LOGIN"
})
const removeAuth = () => ({
type: "LOGOUT"
})
// <Login> component
const Login = ({ dispatch }) => {
const login = e => {
e.preventDefault()
dispatch(authenticate())
}
return (
<form onSubmit={login}>
<button type="Submit">Login</button>
</form>
)
}
// <LoginPage> component
const LoginContainer = connect()(Login)
const ProtectedPage = ({ dispatch }) => {
const logout = e => {
e.preventDefault()
dispatch(removeAuth())
}
return (
<div>
<h1>Protected</h1>
<form onSubmit={logout}>
<button type="Submit">Logout</button>
</form>
</div>
)
}
const ProtectedPageContainer = connect()(ProtectedPage)
class App extends Component {
render() {
return (
<Provider store={store}>
<Router>
<CheckAuthContainer
LoginPage={LoginContainer}
Protected={ProtectedPageContainer}
location={location}
/>
</Router>
</Provider>
)
}
}
export default App
package.json:
{
"name": "test-redux-router-redirect",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-redux": "^5.0.4",
"react-router-dom": "^4.1.1",
"redux": "^3.6.0",
"redux-logger": "^3.0.1",
"redux-thunk": "^2.2.0"
},
"devDependencies": {
"react-scripts": "0.9.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
Upvotes: 3
Views: 1945
Reputation: 157
I figured it out.
The React Router documentation's section on Redux integration actually addresses this case directly, but somehow I misunderstood how to apply it.
Passing the <CheckAuthContainer>
component into withRouter
solved my issue. So line 45 looks like this:
const CheckAuthContainer = withRouter(connect(mapStateToProps)(CheckAuth))
I haven't tried this out in the real app I'm working on yet. Hopefully it works there too!
Upvotes: 4