【问题标题】:Modal dialog auth with react-router带有 react-router 的模态对话框身份验证
【发布时间】:2016-04-26 18:34:32
【问题描述】:

我有带有 publicprivate 部分的 react/redux/react-router 应用程序。 登录和注册表单显示为模式对话框,没有自己的路由。

所需流量: 用户点击链接 -> 模式对话框显示在当前页面 -> 以防身份验证成功转换到链接页面,否则将用户留在当前页面。

如果没有当前页面-显示索引页面并继续流程

我尝试使用 onEnter 钩子将其存档,但据我所知,转换发生在钩子执行之前。如果我尝试使用 history.goBack() 它会导致页面重新呈现并且看起来很糟糕。

有没有什么方法可以解决这个问题而无需不必要的重定向和额外的渲染调用?

【问题讨论】:

    标签: javascript authentication reactjs react-router


    【解决方案1】:

    好的 - 我想我想出了一种方法来处理这个问题,它涵盖了所有的极端情况。不过,它确实要求您有某种方法可以从几乎任何组件访问应用程序状态。为此,我正在使用 Redux。这也假设登录、注册等没有路由。

    我所做的是创建两个“包装器”组件。第一个封装了所有不安全的路由并将位置存储到一个状态值中,这样我们总是可以引用最后一个不安全的路由...

    import { Component } from 'react';
    import { connect } from 'react-redux';
    import { bindActionCreators } from 'redux';
    import { setRoute } from './redux/client/ducks/auth';
    
    function mapDispatchToProps(dispatch) {
      return {
        setRoute: bindActionCreators(setRoute, dispatch)
      };
    }
    
    @connect(null, mapDispatchToProps)
    export default class InsecureWrapper extends Component {
    
      componentDidMount() {
        const { location, setRoute } = this.props;
        setRoute(location.pathname);
      }
    
      render() {
        return (
          <div>
            {this.props.children}
          </div>
        )
      }
    }
    

    另一个包裹所有安全路由。它显示登录对话框(也可以在登录和注册之间来回切换)并防止显示内容,除非登录到应用程序...

    import { Component } from 'react';
    import { connect } from 'react-redux';
    import { bindActionCreators } from 'redux';
    import * as authActions from './redux/client/ducks/auth';
    
    function mapStateToProps(state) {
      return {
        auth: state.auth
      };
    }
    
    function mapDispatchToProps(dispatch) {
      return {
        authActions: bindActionCreators(authActions, dispatch)
      };
    }
    
    @connect(
      mapStateToProps,
      mapDispatchToProps
    )
    export default class SecureWrapper extends Component {
    
      componentDidMount() {
        const { auth, authActions } = this.props;
        //see if user and if not prompt for login
        if(!auth.loggedIn) {
          authActions.openLogin();
        }
      }
    
      //close any open dialogs in case the user hit browser back or whatever 
      componentWillUnmount() {
        const { authActions } = this.props;
        authActions.resetAuthDialogs();
      }    
    
      render() {
        const { auth, children } = this.props;
        return (
            <div className="container">
              {auth.loggedIn &&
                {children} ||
                <span>YOU MUST BE LOGGED IN TO VIEW THIS AREA!</span>
              }
            </div>
        );
      }
    
    }
    

    然后在路由中,根据需要将它们包装在包装器中......

    import App from './containers/App';
    import Dashboard from './containers/Dashboard';
    import Secure from './containers/Secure';
    import AuthWrapper from './common/client/components/AuthWrapper';
    import InsecureWrapper from './common/client/components/InsecureWrapper';
    
    export default [
      {path: '/', component: App, //common header or whatever can go here
      childRoutes: [
        {component: InsecureWrapper,
          childRoutes: [ //<- ***INSECURE ROUTES ARE CHILDREN HERE***
            {path: 'dashboard', component: Dashboard}
          ]
        },
        {component: SecureWrapper,
          //***SECURE ROUTES ARE CHILDREN HERE***
          childRoutes: [
            {path: 'secure', component:Secure}
          ]
        }
      ]}
    ]
    

    最后但并非最不重要...在您的对话框中,您需要通过将位置推送(或替换)到保存的状态值来处理取消。当然,在成功登录后,只需关闭它们...

    import { Component } from 'react';
    import { connect } from 'react-redux';
    import { bindActionCreators } from 'redux';
    import * as authActions from './redux/client/ducks/auth';
    import LoginDialog from './common/client/components/dialogs/LoginDialog';
    import RegisterDialog from './common/client/components/dialogs/RegisterDialog';
    // this could also be replacePath if you wanted to overwrite the history
    import { pushPath } from 'redux-simple-router'; 
    
    function mapStateToProps(state) {
      return {
        auth: state.auth
      };
    }
    
    function mapDispatchToProps(dispatch) {
      return {
        authActions: bindActionCreators(authActions, dispatch),
        pushPath: bindActionCreators(pushPath, dispatch),
      };
    }
    
    @connect(
      mapStateToProps,
      mapDispatchToProps
    )
    export default class AuthContainer extends Component {
    
      _handleLoginCancel = (e) => {
        const { auth, authActions, pushPath } = this.props;
        pushPath(auth.prevRoute); // from our saved state value
        authActions.closeLogin();
      };
    
      _handleLoginSubmit = (e) => {
        const { authActions } = this.props;
        // do whatever you need to login here
        authActions.closeLogin();
      };
    
      render() {
        const { auth } = this.props;
        return (
          <div>
            <LoginDialog
              open={auth.showLogin}
              handleCancel={this._handleLoginCancel}
              handleSubmit={this._handleLoginSubmit}
              submitLabel="Login"
            />
           ...
          </div>
        )
      }
    }
    

    我显然在使用 ES6、Babel 和 webpack……但原则应该在没有它们的情况下适用,因为不应该使用 Redux(你可以将 prev 路由存储在本地存储或其他东西中)。为了简洁起见,我还省略了一些传递道具的中间组件。

    其中一些可能是功能组件,但我将它们保留为完整以显示更多细节。通过抽象其中的一些,还有一些改进的空间,但我再次将其保留为多余的以显示更多细节。希望这会有所帮助!

    【讨论】:

    • 如果没有先前的路由,我忘记了关于索引的部分,但如果先前的路由为空/null,那只是推送/替换上的一个 IF。
    • 虽然提供了很好的流程来处理直接导航到私人页面,但它对主要问题没有帮助 - 在当前页面上显示模式窗口,而不是在目标页面上。我一直在寻找某种位置更改前回调,但看起来我只需要扩展链接标签
    • 是的,我想了这么多,并且删除了 onTransitionTo,还没有找到更好的方法来处理它。这确实非常接近,因为如果未登录,它会隐藏目标页面上的内容,并且除了登录之外唯一的“退出”是取消 - 路由返回到它们来自的页面(或“主页”)。我的对话框可以从任何页面启动,因此效果很好,只需将其保存在路线中的一个位置即可。抱歉,对于您想要的内容无法提供更多帮助。
    猜你喜欢
    • 1970-01-01
    • 2018-07-31
    • 2021-08-11
    • 1970-01-01
    • 2018-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-14
    相关资源
    最近更新 更多