user6689821
user6689821

Reputation: 157

Routes/Redirects do not render in a React Router 4 & Redux Authorization

I'm trying to implement a simple authorization flow using React Router 4 + Redux.

The behavior I want

My Implementation

The issue

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!

The code

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

Answers (1)

user6689821
user6689821

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

Related Questions