React router private routes / redirect not working

时间:2017-04-20 13:08:50

标签: reactjs redux react-router react-redux

我稍微调整了React Router示例,私有路由可以很好地与Redux一起使用,但在链接或重定向到其他“页面”时不会呈现任何组件。这个例子可以在这里找到:

https://reacttraining.com/react-router/web/example/auth-workflow

他们的PrivateRoute组件如下所示:

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

但是,因为我已将其合并到Redux应用程序中,所以我必须稍微调整一下PrivateRoute,以便我可以访问redux存储以及路径Props:

const PrivateRouteComponent = (props) => (
    <Route {...props.routeProps} render={() => (
    props.logged_in ? (
        <div>{props.children}</div>
        ) : (
        <Redirect to={{
            pathname: '/login',
            state: { from: props.location }
        }} /> )
    )} />
);

const mapStateToProps = (state, ownProps) => {
    return {
        logged_in: state.auth.logged_in,
        location: ownProps.path,
        routeProps: {
            exact: ownProps.exact,
            path: ownProps.path
        }
    };
};

const PrivateRoute = connect(mapStateToProps, null)(PrivateRouteComponent);
export default PrivateRoute

每当我没有登录并点击PrivateRoute时,我都被正确地重定向到/ login。但是,在例如登录并使用<Redirect .../>或单击任何<Link ...>到PrivateRoute之后,URI会更新,但视图不会更新。它停留在同一个组件上。

我做错了什么?

只是为了完成图片,在应用程序的index.js中有一些不相关的东西,路由设置如下:

ReactDOM.render(
    <Provider store={store}>
        <App>
            <Router>
                <div>
                    <PrivateRoute exact path="/"><Home /></PrivateRoute>
                    // ... other private routes
                    <Route path="/login" component={Login} />
                </div>
            </Router>
        </App>
    </Provider>,
    document.getElementById('root')
);

9 个答案:

答案 0 :(得分:11)

您需要使用Route代码

包裹<Switch>
ReactDOM.render(
<Provider store={store}>
    <App>
        <Router>
            <div>
                <Switch>
                   <PrivateRoute exact path="/"><Home /></PrivateRoute>
                   // ... other private routes
                   <Route path="/login" component={Login} />
                </Switch>
            </div>
        </Router>
    </App>
</Provider>,
document.getElementById('root'));

答案 1 :(得分:8)

将私有路由设置为不纯:

export default connect(mapStateToProps, null, null, {
  pure: false,
})(PrivateRoute);

这将让Component重新渲染。

请参阅:react-router-4-x-privateroute-not-working-after-connecting-to-redux

答案 2 :(得分:6)

刚出现同样的问题,我通过制作我的App redux容器并将isAuthenticated作为PropR传递给PrivateRoute来解决它

在这里,我希望它有所帮助

const App = (props) => {
return (
  <Provider store={store}>
    <Router>
      <div>
        <PrivateRoute path="/secured" component={Secured} isAuthenticated={props.isAuthenticated} />
      </div>
    </Router>
  </Provider>
  );
};

const mapStateToProps = state => ({
  isAuthenticated: state.isAuthenticated
});

export default connect(mapStateToProps)(App);

然后在我的PrivateRoute

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

export default PrivateRoute;

答案 3 :(得分:5)

我设法使用rest参数来访问来自mapStateToProps的数据:

const PrivateRoute = ({component: Component, ...rest}) => {
  const {isAuthenticated} = rest;

  return (
    <Route {...rest} render={props => (
      isAuthenticated ? (
        <Component {...props}/>
      ) : (
        <Redirect to={{
          pathname: '/login',
          state: {from: props.location}
        }}/>
      )
    )}
    />
  );
};

PrivateRoute.propTypes = {
  isAuthenticated: PropTypes.bool.isRequired,
};

function mapStateToProps(state) {
  return {
    isAuthenticated: state.user.isAuthenticated,
  };
}

export default connect(mapStateToProps)(PrivateRoute);

答案 4 :(得分:4)

嗯,我认为这个问题的答案应该更加详细,所以在这里,我经过了4个小时的挖掘。

当你用connect()包装你的组件时,React Redux在它上面实现了shouldComponentUpdate(如果你在github问题上搜索答案的sCU)并对props进行浅层比较(它通过props对象中的每个键进行检查并检查值是否为与&#39; ===&#39;相同。实践中意味着您的组件被视为 Pure 现在只有当它的道具发生变化时才会改变!这是这里的关键信息。 第二个关键消息,React路由器使用上下文将位置,匹配和历史对象从路由器传递到路由组件。 它不使用道具

现在让我们在实践中看到发生了什么,因为即使知道这一点,我发现它仍然相当棘手:

  • 案例 1

连接后你的道具有3个键:path,component和auth(由connect给出)。因此,事实上,您的包裹组件在路线更改时根本不会重新渲染,因为它并不关心。当路线改变时,你的道具不会改变,也不会更新。

  • 案例 3

现在连接后你的道具有4个键:path,component,auth和anyprop。这里的技巧是anyprop是每次调用组件时创建的对象。因此,无论何时调用组件,都会进行以下比较:{a:1} === {a:1},(您可以尝试)给出错误,因此您的组件现在每次都会更新。但请注意,您的组件仍然不关心路线,它的孩子会这样做。

  • 案例 2

现在这是个谜,因为我猜你在你的App文件中调用了这一行,并且不应该引用&#34; auth&#34;在那里,你应该有一个错误(至少那是我在说什么)。我的猜测是&#34; auth&#34;在您的App文件中引用在那里定义的对象。

现在该怎么办?

我在这里看到两个选项:

  1. 告诉React Redux您的组件不纯,这将删除sCU注入,您的组件现在将正确更新。

    connect(mapStateToProps,null,null,{   纯:虚假 })(PrivateRoute)

  2. 使用WithRouter(),这将导致将location,match和history对象作为props注入组件。现在,我不知道内部,但我怀疑React路由器不会改变这些对象,所以每次路由改变时,它的道具也会改变(sCU返回true),你的组件也会正确更新。这样做的副作用是你的React树现在被很多WithRouter和Route东西污染......

  3. 参考github问题:Dealing with Update Blocking

    您可以在此处看到withRouter旨在作为quickfix而非推荐的解决方案。使用pure:false没有提及,所以我不知道这个修复有多好。

    我找到了第三个解决方案,虽然我不清楚它是否真的是比使用高阶组件更好的解决方案。您将高阶组件连接到Redux商店,现在您的路线并没有对它所呈现的内容感到厌恶,HOC处理它。

    import Notlogged from "./Notlogged";    
    function Isloggedhoc({ wrap: Component, islogged, ...props }) {
          return islogged ? <Component {...props} /> : <Notlogged {...props} />;
        }
    
    const mapState = (state, ownprops) => ({
          islogged: state.logged,
          ...ownprops
        });
    
    export default connect(mapState)(Isloggedhoc);
    
    你app.js中的

    <Route path="/PrivateRoute" render={props => <Isloadedhoc wrap={Mycomponent} />} />
    

    你甚至可以做一个curry函数来缩短它:

    function isLogged(component) {
      return function(props) {
        return <Isloadedhoc wrap={component} {...props} />;
      };
    }
    

    像这样使用它:

    <Route path="/PrivateRoute" render={isLogged(Mycomponent)} />
    

答案 5 :(得分:3)

我也一直在努力解决这个问题,这是我的解决方案。

而不是传递isAuthenticated到每个&lt; PrivateRoute&GT;组件,你只需要得到isAuthenticated from state in&lt; PrivateRoute&GT;本身。

import React from 'react';
import {Route, Redirect, withRouter} from 'react-router-dom';
import {connect} from 'react-redux';

// isAuthenticated is passed as prop here
const PrivateRoute = ({component: Component, isAuthenticated , ...rest}) => {
    return <Route
        {...rest}
        render={
            props => {
                return isAuthenticated ?
                    (
                        <Component {...props} />
                    )
                    :
                    (
                        <Redirect
                            to={{
                                pathname: "/login",
                                state: {from: props.location}
                            }}
                        />
                    )
            }
        }
    />
};

const mapStateToProps = state => (
    {
        // isAuthenticated  value is get from here
        isAuthenticated : state.auth.isAuthenticated 
    }
);

export default withRouter(connect(
    mapStateToProps, null, null, {pure: false}
)(PrivateRoute));

答案 6 :(得分:1)

根据react-router documentation,您可以使用connect包裹withRouter函数:

// before
export default connect(mapStateToProps)(Something)

// after
import { withRouter } from 'react-router-dom'
export default withRouter(connect(mapStateToProps)(Something))

这对我有用,我的观点在这种情况下开始与路线一起更新。

答案 7 :(得分:0)

我有像@Rein这样的类似问题。就我而言,PrivateRoute看起来与原始版本几乎相同,但只连接到Redux并在原始示例中使用它而不是fakeAuth。

## Number of random data frames to create:
n <- 10

## Sample vector of seeds:
initSeed <- 1234
set.seed(initSeed)
seedVec <- sample.int(n = 1e8, size = n, replace = FALSE)

## loop:
lst <- lapply(1:n, function(i){
            set.seed(seedVec[i])
            a <- rnorm(10,.9,.05)
            b <- sample(8:200,10,replace=TRUE)
            c <- rnorm(10,80,30)
            data.frame(a,b,c)
        })
## Set names with leading zeroes (2 digits). If you want
## three digits, change "%02d" to "%03d" etc.
names(lst) <- paste0('df', sprintf("%02d", 1:10))

用法和结果: -

  1. 不工作,但期待工作
    • const PrivateRoute = ({ component: Component, auth, ...rest }) => ( <Route {...rest} render={props => auth.isAuthenticated ? <Component {...props} /> : <Redirect to={{ pathname: "/login" }} />} /> ); PrivateRoute.propTypes = { auth: PropTypes.object.isRequired, component: PropTypes.func.isRequired } const mapStateToProps = (state, ownProps) => { return { auth: state.auth } }; export default connect(mapStateToProps)(PrivateRoute);
  2. 工作但不希望像这样使用
    • <PrivateRoute path="/member" component={MemberPage} />
  3. 工作。只是工作,但根本不想使用。从这一点可以理解,如果你将原始的PrivateRoute连接到Redux,你需要传递一些额外的道具(任何道具)来使PrivateRoute工作,否则它不起作用。 任何人,请对此行为提供一些提示。这是我的主要关注点。 As a New Question at
    • <PrivateRoute path="/member" component={MemberPage} auth={auth} />

答案 8 :(得分:0)

打字稿

如果您正在寻找Typescript的解决方案,那么我就以这种方式使它起作用,

const PrivateRoute = ({ component: Component, ...rest }: any) => (
    <Route
        {...rest}
        render={props =>
            localStorage.getItem("authToken") ? (
                <Component {...props} />
            ) : (
                    <Redirect
                        to={{
                            pathname: "/login",
                            state: { from: props.location }
                        }}
                    />
                )
        }
    />
);

<Router>
    <Switch>
        <PrivateRoute exact path="/home" component={Home} />
        <Route path="/login" component={Login} />
    </Switch>
</Router>

以防万一,您需要创建一个类,然后再执行类似的操作

class PrivateRoute extends Route {
    render() {
        if (localStorage.getItem("authToken")) {
            return <Route {...this.props} />
        } else {
            return <Redirect
                to={{
                    pathname: "/login",
                    state: { from: this.props.location }
                }}
            />
        }
    }
}