我正在为我正在开发的应用程序进行身份验证过程。
当前,用户通过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);
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;
答案 0 :(得分:0)
componentDidMount()
将在 首次渲染后运行。因此,第一次渲染时,this.props.user
内部将是null
。
您可以:
将异步调用移至componentWillMount()
(不推荐)
在SiteWrapper
()中放一个警卫,以便它可以处理null
案件
在异步调用完成之前不渲染Home
中的App
答案 1 :(得分:0)
在componentDidMount()中,您期望设置用户。
您可以:
答案 2 :(得分:0)
您当然可以检查是否为空,并且不要尝试在SiteWrapper的componentDidMount()方法中显示用户的详细信息。但为什么?如果找不到您的用户并且该用户为null,您还有其他选择吗?我觉得不是。因此,您基本上有两个选项:
理想的解决方案是实施异步操作并显示活动 指示器(例如Spinner),直到加载了jwt-token。然后 您可以提取用户信息并完全呈现您的 成功完成提取后立即添加组件。
如果出于任何原因您都无法使用异步操作,我建议 “ avoid-null ”方法。将默认用户放在初始位置 状态,应该这样做。如果您更新用户道具,则 组件将始终重新渲染(如果连接正确)。
答案 3 :(得分:0)
我已经解决了我的问题。似乎这是少数尝试保持简单和发展失败的案例之一。
现在,在加载应用程序时,我将显示用户信息或登录按钮。
从URL加载密钥后,组件将重新渲染以显示用户信息。
这确实增加了渲染数量,但是,这可能是最好的渲染方法。