我有一个网站。当匿名访问者访问https://mywebsite.com/sign?next=/app,他可以在弹出窗口中通过Google登录,然后https://mywebsite.com/sign?next=/app自动重定向到https://mywebsite.com/app,标题显示他的用户名。大多数情况下,此工作流程都有效。但是,有四分之一,在登录和重定向后,https://mywebsite.com/app 的整个页面都没有通知用户已登录,例如,标题显示
Sign In
。
所以我的问题是如何确保商店中的状态在重定向之前得到很好的更新。这是一些代码。
在
selectors/auth.ts
:
import { State } from '../store/reducer';
import { UserType } from '../services/auth';
export const getIsLoggedIn = (state: State): boolean => {
return !!state.auth.user && state.auth.user.type !== UserType.ANONYM;
};
export const getIsLoggingInOrOut = (state: State): boolean => {
return state.auth.IsLoggingInOrOut;
};
在
models/auth.ts
:
import * as authService from '../services/auth';
import { LOCATION_CHANGE } from 'react-router-redux';
import queryString from 'query-string';
export interface User {
email: string;
exp: number;
googleID: string;
microsoftID: string;
name: string;
type: string;
_id: string;
}
export interface State {
user?: User;
IsLoggingInOrOut: boolean;
loginErrorMsg?: string;
ssoLoginError? : string ;
registerErrorMsg?: string;
next?: string;
}
export default {
namespace: 'auth',
state: {
user: undefined,
IsLoggingInOrOut: false,
},
reducers: {
setIsLoggingInOrOut(state: State, { payload: { IsLoggingInOrOut } }: any) {
return { ...state, IsLoggingInOrOut };
},
save(state: State, { payload: { user, loginErrorMsg, registerErrorMsg } }: any) {
return { ...state, user, loginErrorMsg, registerErrorMsg };
},
update(state: State, { payload: { user, loginErrorMsg, registerErrorMsg , ssoLoginError } }: any) {
return {
...state,
user: user || state.user,
loginErrorMsg: loginErrorMsg || state.loginErrorMsg,
ssoLoginError : ssoLoginError || state.ssoLoginError,
registerErrorMsg: registerErrorMsg || state.registerErrorMsg,
};
},
redirect(state: State, { payload: { next } }: any) {
return { ...state, next };
},
clearError(state: State, { }) {
return { ...state, loginErrorMsg: undefined, registerErrorMsg: undefined , ssoLoginError : undefined };
},
},
effects: {
*fetch({ }, { call, put, take, select, all }: any) {
yield put({
type: 'setIsLoggingInOrOut',
payload: { IsLoggingInOrOut: false },
});
try {
const routing = yield select(({ routing }: any) => routing);
let user;
if (routing === undefined) {
const [u, _] = yield all([call(authService.fetch), take(LOCATION_CHANGE)]);
user = u;
} else {
user = yield call(authService.fetch);
}
yield put({
type: 'save',
payload: { user },
});
yield put({ type: 'clearError' });
} catch {
} finally {
yield put({
type: 'setIsLoggingInOrOut',
payload: { IsLoggingInOrOut: false },
});
}
},
*loginOauth({ payload }: { payload: authService.LoginOauthPayload }, { call, put }: any) {
const user = yield call(authService.loginOauth, payload);
yield put({
type: 'save',
payload: { user },
});
yield put({ type: 'clearError' });
},
},
subscriptions: {
setup({ dispatch, history }: any) {
return history.listen(({ pathname, search }: any) => {
const query = queryString.parse(search);
let next: string | undefined | null;
if (query) {
if (query.next instanceof Array) {
next = query.next[0];
} else {
next = query.next;
}
}
if (pathname === '/sign') {
if (next === "/reset")
dispatch({ type: 'redirect', payload: { next: "/" } });
else
dispatch({ type: 'redirect', payload: { next } });
} else if ((pathname === '/reset') || (pathname === "/")) {
} else {
dispatch({ type: 'redirect', payload: { next: undefined } });
}
dispatch({ type: 'fetch' });
});
},
},
};
在
services/auth.ts
:
export async function fetch(): Promise<any> {
const token = await getSignToken();
let user;
try {
user = payloadFromToken(token);
if (user.exp) {
if (Date.now() > user.exp * 1000) {
throw new Error('token has expired');
}
}
} catch {
user = await registerAnonym();
}
if (!user) {
user = await logout();
}
return user;
}
在
selectors/header.ts
:
import { push } from 'react-router-redux';
import { ICommandBarItemProps } from 'office-ui-fabric-react';
import { State } from '../store/reducer';
import { DEFAULT_PATH } from '../constants';
import { getIsLoggedIn, getNext, getUserName, getIsLoggingInOrOut } from './auth';
export interface IHeaderItem extends ICommandBarItemProps {
actionCreator?: () => { type: string; payload?: any };
}
export const getNextPath = (state: State): string => {
let path: string = DEFAULT_PATH;
if (state.routing.location) {
path = state.routing.location.pathname;
if (path === '/sign') {
path = getNext(state);
}
}
return path;
};
export const getFarItems = (state: State): IHeaderItem[] => {
const isLoggedIn: boolean = getIsLoggedIn(state);
const isLoggingInOrOut: boolean = getIsLoggingInOrOut(state);
const path = getNextPath(state);
const logout = () => ({
type: 'auth/logout',
});
const login = () => {
return push(`/sign?next=${path}`);
};
const toMySubscription = () => {
return push(`/my-subscription`);
};
const username = getUserName(state);
return [
{
key: 'account',
text: isLoggedIn ? username : 'Sign In',
ariaLabel: isLoggedIn ? 'Sign Out' : 'Sign In',
subMenuProps: isLoggedIn
? {
items: [
{
key: 'signout',
text: 'Sign Out',
actionCreator: logout,
},
],
}
: undefined,
iconOnly: false,
actionCreator: isLoggingInOrOut ? undefined : !isLoggedIn ? login : undefined,
},
];
}
};
在
Main/Header/index.tsx
:
import React, { Component } from 'react';
import { Dispatch } from 'redux';
import { connect } from 'dva';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import { PersonaSize, PersonaCoin } from 'office-ui-fabric-react/lib/Persona';
import { Icon } from 'office-ui-fabric-react';
import CommonHeader from '../../Header';
import { ThemeContext } from '../../Theme';
import selectors from '../../../selectors';
import { State as IReduxState } from '../../../store/reducer';
import { IHeaderItem } from '../../../selectors/header';
export const convertActionCreatorToOnClick = (
item: IHeaderItem,
dispatch: any,
): IHeaderItem => ({
...item,
onClick: item.actionCreator
? () => item.actionCreator && dispatch(item.actionCreator())
: undefined,
subMenuProps: item.subMenuProps
? {
...item.subMenuProps,
items:
item.subMenuProps.items !== undefined
? item.subMenuProps.items.map(subItem => ({
...subItem,
onClick: subItem.actionCreator
? () => dispatch(subItem.actionCreator())
: undefined,
}))
: [],
}
: undefined,
});
interface IProps {
items: IHeaderItem[];
farItems: IHeaderItem[];
dispatch: Dispatch<any>;
isLoggedIn: boolean;
isLoggingInOrOut: boolean;
profilePicUrl?: string;
}
interface IState {
isSolutionSettingsVisible?: boolean;
}
class Header extends Component<IProps, IState> {
private renderItem = (item: IHeaderItem): IHeaderItem => {
const customRenderIcons = this.getCustomOnRenderIconButtons();
const onClickReadyItem = convertActionCreatorToOnClick(item, this.props.dispatch);
if (item.key in customRenderIcons) {
return {
...onClickReadyItem,
onRenderIcon: customRenderIcons[item.key],
};
} else {
return onClickReadyItem;
}
};
private getCustomOnRenderIconButtons = (): { [key: string]: () => JSX.Element } => {
const { isLoggedIn, isLoggingInOrOut, profilePicUrl } = this.props;
const loginElement = isLoggedIn ? (
<ThemeContext.Consumer>
{theme => (
<PersonaCoin
imageUrl={profilePicUrl}
size={PersonaSize.size28}
initialsColor="white"
styles={{
initials: {
color: (theme && theme.primary) || 'black',
},
}}
/>
)}
</ThemeContext.Consumer>
) : (
// <Icon iconName="Leave" title="SignIn" ariaLabel="SignIn" />
<Icon title="SignIn" ariaLabel="SignIn" />
);
return {
account: () => (
// <div style={{ width: '28px', overflow: 'hidden' }}>
<div>
{isLoggingInOrOut ? <Spinner size={SpinnerSize.medium} /> : loginElement}
</div>
),
};
};
render() {
const { items, farItems } = this.props;
return (
<>
<div className="header">
<CommonHeader
items={items.map((item: IHeaderItem) => this.renderItem(item))}
farItems={farItems.map((item: IHeaderItem) => this.renderItem(item))}
/>
</div>
</>
);
}
}
export default connect((state: IReduxState) => ({
items: selectors.header.getItems(state),
farItems: selectors.header.getFarItems(state),
isLoggedIn: selectors.auth.getIsLoggedIn(state),
isLoggingInOrOut: selectors.auth.getIsLoggingInOrOut(state),
}))(Header);
有人能帮忙吗?