组件渲染之前未填充Redux存储

时间:2018-12-05 12:20:39

标签: reactjs redux

我正在为我正在开发的应用程序进行身份验证过程。

当前,用户通过Steam登录。登录验证通过后,服务器会将用户重定向到应用程序索引/,并向他们发出一对JWT作为GET变量。然后,该应用程序将它们存储在Redux存储中,然后出于安全目的重写URL来隐藏JWT令牌。

然后,应用程序对令牌进行解码,以获取有关用户的信息,例如用户名和头像地址。该应该呈现在应用程序的SiteWrapper组件中,但是,这是我的问题所在。

似乎正在发生的事情是,SiteWrapper组件在App组件完成保存标记之前被加载,因此由于未定义变量而引发错误。似乎相关的大多数修补程序都是针对API请求的,但是在这种情况下,情况并非如此。 URL中已经有数据。我不确定是否同样适用。

有关如何解决此问题的任何建议?任何其他最佳实践建议将不胜感激。我是React和Redux的新手。

错误

Error

索引

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

import { Provider } from 'react-redux';
import store from './redux/store';

// console debug setup
window.store = store;

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root'));
serviceWorker.unregister();

应用

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import { connect } from 'react-redux';
import "tabler-react/dist/Tabler.css";

import history from './utils/history';

import {
    storeRefreshJWTToken,
    storeAccessJWTToken,
    loadUserFromJWTRefreshToken
} from "./redux/app";

import {
  HomePage
} from './pages';

class App extends Component {
    componentDidMount() {
        //Get tokens from URL when app loads and then hide them from url.
        const urlParams = new URLSearchParams(window.location.search);
        if(urlParams.has('access_token') && urlParams.has('refresh_token')){
            this.props.storeRefreshJWTToken(urlParams.get('refresh_token'));
            this.props.storeAccessJWTToken(urlParams.get('access_token'));

            //Load user info from obtained tokens.
            this.props.loadUserFromJWTRefreshToken();
        }
        history.push('/');
    }

    render() {
        return (
            <React.StrictMode>
              <Router basename={process.env.PUBLIC_URL} history={history}>
                <Switch>
                  <Route exact path="/" component={HomePage}/>
                </Switch>
              </Router>
            </React.StrictMode>
        );
    }
}

const mapStateToProps = (state) => ({});

const mapDispatchToProps = {
    storeRefreshJWTToken,
    storeAccessJWTToken,
    loadUserFromJWTRefreshToken
};

export default connect(mapStateToProps, mapDispatchToProps)(App);

SiteWrapper

import * as React from "react";
import { withRouter } from "react-router-dom";
import { connect } from 'react-redux';

import {
    Site,
    Nav,
    Grid,
    List,
    Button,
    RouterContextProvider,
} from "tabler-react";

class SiteWrapper extends React.Component {
    componentDidMount() {
        console.log(this.props);
        this.accountDropdownProps = {
            avatarURL: this.props.user.avatar,
            name: this.props.user.display_name,
            description: "temp",
            options: [
                {icon: "user", value: "Profile"},
                {icon: "settings", value: "Settings"},
                {isDivider: true},
                {icon: "log-out", value: "Sign out"},
            ],
        };
    }

    render(){
        return (
            <Site.Wrapper
                headerProps={{
                    href: "/",
                    alt: "Tabler React",
                    imageURL: "./demo/brand/tabler.svg",
                    navItems: (
                        <Nav.Item type="div" className="d-none d-md-flex">
                            <Button
                                href="https://github.com/tabler/tabler-react"
                                target="_blank"
                                outline
                                size="sm"
                                RootComponent="a"
                                color="primary"
                            >
                                Source code
                            </Button>
                        </Nav.Item>
                    ),
                    accountDropdown: this.accountDropdownProps,
                }}
                navProps={{ itemsObjects: this.props.NavBarLinks }}
                routerContextComponentType={withRouter(RouterContextProvider)}
                footerProps={{
                    copyright: (
                        <React.Fragment>
                            Copyright © 2018
                            <a href="https://thomas-smyth.co.uk/"> Thomas Smyth</a>.
                            All rights reserved.
                        </React.Fragment>
                    ),
                    nav: (
                        <React.Fragment>
                            <Grid.Col auto={true}>
                                <List className="list-inline list-inline-dots mb-0">
                                    <List.Item className="list-inline-item">
                                        <a href="/developers">Developers</a>
                                    </List.Item>
                                    <List.Item className="list-inline-item">
                                        <a href="/faq">FAQ</a>
                                    </List.Item>
                                </List>
                            </Grid.Col>
                        </React.Fragment>
                    ),
                }}
            >
                {this.props.children}
            </Site.Wrapper>
        );
    }
}

const mapStateToProps = (state) => {
    console.log(state);
    return {
        user: state.App.user
    };
};


export default connect(mapStateToProps)(SiteWrapper);

减速器

import initialState from './initialState';
import jwt_decode from 'jwt-decode';

//JWT Auth
const STORE_JWT_REFRESH_TOKEN = "STORE_JWT_REFRESH_TOKEN";
export const storeRefreshJWTToken = (token) => ({type: STORE_JWT_REFRESH_TOKEN, refresh_token: token});

const STORE_JWT_ACCESS_TOKEN = "STORE_JWT_ACCESS_TOKEN";
export const storeAccessJWTToken = (token) => ({type: STORE_JWT_ACCESS_TOKEN, access_token: token});

// User
const LOAD_USER_FROM_JWT_REFRESH_TOKEN = "DEC0DE_JWT_REFRESH_TOKEN";
export const loadUserFromJWTRefreshToken = () => ({type: LOAD_USER_FROM_JWT_REFRESH_TOKEN});

export default function reducer(state = initialState.app, action) {
    switch (action.type) {
        case STORE_JWT_REFRESH_TOKEN:
            return {
                ...state,
                jwtAuth: {
                    ...state.jwtAuth,
                    refresh_token: action.refresh_token
                }

            };
        case STORE_JWT_ACCESS_TOKEN:
            return {
                ...state,
                JWTAuth: {
                    ...state.jwtAuth,
                    access_token: action.access_token
                }

            };
        case LOAD_USER_FROM_JWT_REFRESH_TOKEN:
            const user = jwt_decode(state.jwtAuth.refresh_token);
            return {
                ...state,
                user: user
            };
        default:
            return state;
    }
}

商店

import { createStore, combineReducers, applyMiddleware  } from 'redux';
import ReduxThunk from 'redux-thunk'

import App from './app';

const combinedReducers = combineReducers({
    App
});

const store = createStore(combinedReducers, applyMiddleware(ReduxThunk));

export default store;

4 个答案:

答案 0 :(得分:0)

componentDidMount()将在 首次渲染后运行。因此,第一次渲染时,this.props.user内部将是null

您可以:

  • 将异步调用移至componentWillMount()(不推荐)

  • SiteWrapper()中放一个警卫,以便它可以处理null案件

  • 在异步调用完成之前不渲染Home中的App

答案 1 :(得分:0)

在componentDidMount()中,您期望设置用户。

您可以:

  • 使用一些可以轻松检查的“ isAuth”标志将用户设置为初始状态
  • 不推荐使用componentWillMount(),您可以使用componentDidUpdate()检查用户状态是否已更改并执行一些操作。
  • 锯YWT时使用asyns函数

答案 2 :(得分:0)

您当然可以检查是否为空,并且不要尝试在SiteWrapper的componentDidMount()方法中显示用户的详细信息。但为什么?如果找不到您的用户并且该用户为null,您还有其他选择吗?我觉得不是。因此,您基本上有两个选项:

  1. 理想的解决方案是实施异步操作并显示活动 指示器(例如Spinner),直到加载了jwt-token。然后 您可以提取用户信息并完全呈现您的 成功完成提取后立即添加组件。

  2. 如果出于任何原因您都无法使用异步操作,我建议 “ avoid-null ”方法。将默认用户放在初始位置 状态,应该这样做。如果您更新用​​户道具,则 组件将始终重新渲染(如果连接正确)。

答案 3 :(得分:0)

我已经解决了我的问题。似乎这是少数尝试保持简单和发展失败的案例之一。

现在,在加载应用程序时,我将显示用户信息或登录按钮。

从URL加载密钥后,组件将重新渲染以显示用户信息。

这确实增加了渲染数量,但是,这可能是最好的渲染方法。