首页 > 解决方案 > 在组件渲染之前未填充 Redux Store

问题描述

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

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

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

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

对于如何解决这个问题,有任何的建议吗?任何其他最佳实践建议将不胜感激。我是 React 和 Redux 的新手。

错误

错误

指数

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);

站点包装器

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;

标签: reactjsredux

解决方案


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

你可以:

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

  • 在 () 中放置一个守卫,SiteWrapper以便它可以处理null案例

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


推荐阅读