【发布时间】:2021-05-29 11:43:54
【问题描述】:
我有两个组件,用户和 AppSettings。我正在尝试在 App 组件中访问他们的商店。通过阅读 react redux 帮助文档,我尝试了几种不同的方法,但我无法弄清楚如何让我的 mapDispatchToProps 函数在 App 中工作。我得到的最接近的是下面的代码,它抛出错误 TypeError: this.props.requestUser is not a function
有谁知道我应该如何构建我的 mapDispatchToProps 以从两个商店正确导入我的 actionCreators?还是我有其他问题并且在错误的位置打猎?非常感谢任何帮助,谢谢。
错误截图
33 | }
34 |
35 | private fetchUser() {
> 36 | this.props.requestUser();
| ^ 37 | }
38 |
39 | public render() {
AppSettings.ts
import { Action, Reducer } from 'redux';
import { AppThunkAction } from './';
// app settings state
export interface AppSettingsState {
isLoading: boolean;
appSettings: AppSettings;
}
export interface AppSettings {
SiteTitle: string;
PrimaryBackgroundColor: string;
PrimaryFontColor: string;
FooterLinkColor: string;
}
// ACTIONS - descriptions of state transitions
interface RequestAppSettingsAction {
type: 'REQUEST_APP_SETTINGS';
}
interface ReceiveAppSettingsAction {
type: 'RECEIVE_APP_SETTINGS';
appSettings: AppSettings;
}
/* Declare a 'discriminated union' type. */
type KnownAction = RequestAppSettingsAction | ReceiveAppSettingsAction;
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
// They don't directly mutate state, but they can have external side-effects (such as loading data).
export const actionCreators = {
requestAppSettings: (): AppThunkAction<KnownAction> => (dispatch, getState) => {
/* Only load data if it's something we don't already have (and are not already loading) */
console.log("AppSettings fired");
const appState = getState();
if (appState && appState.appSettings) {
fetch(`appsettings`)
.then(response => response.json() as Promise<AppSettings>)
.then(data => {
dispatch({ type: 'RECEIVE_APP_SETTINGS', appSettings: data });
});
dispatch({ type: 'REQUEST_APP_SETTINGS'});
}
}
};
/*
REDUCER - For a given state and action, returns the new state.
To support time travel, this must not mutate the old state.
*/
const unloadedState: AppSettingsState = {
isLoading: false,
appSettings: {
SiteTitle: "",
PrimaryBackgroundColor: "",
PrimaryFontColor: "",
FooterLinkColor: ""
}
};
export const reducer: Reducer<AppSettingsState> = (state: AppSettingsState | undefined, incomingAction: Action): AppSettingsState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case 'REQUEST_APP_SETTINGS':
return {
appSettings: state.appSettings,
isLoading: true
};
case 'RECEIVE_APP_SETTINGS':
return {
appSettings: action.appSettings,
isLoading: false
};
break;
}
return state;
};
User.ts
import { Action, Reducer } from 'redux';
import { isNullOrUndefined } from 'util';
import { AppThunkAction } from './';
import * as Cookies from '../Utilities/cookies';
export interface UserState {
isLoading: boolean;
loginError: string;
loggedIn: boolean;
user: User;
}
export interface User {
username: string;
email: string;
firstName: string;
lastName: string;
token: string
}
interface RequestUserAction {
type: 'REQUEST_USER';
}
interface ReceiveUserAction {
type: 'RECEIVE_USER';
loginError: string;
user: User;
}
type KnownAction = RequestUserAction | ReceiveUserAction;
export const actionCreators = {
requestUser: (event?: React.FormEvent<HTMLFormElement>) : AppThunkAction<KnownAction> => (dispatch, getState) => {
const appState = getState();
var url = "";
var skip = false;
var params = {};
/* Two ways to get user info - they submitted the login form
* or they are already logged in and have a user object cookie */
if (event != undefined) {
event.preventDefault();
const target = event.target as typeof event.target & {
username: { value: string };
password: { value: string };
};
const username = target.username.value;
const password = target.password.value;
url = "user/login";
params = {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username: username, password: password })
}
} else {
var cUser: string | null = Cookies.getCookie("user");
if (cUser) {
var user: User = JSON.parse(cUser);
url = "user";
params = {
method: "GET",
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + user.token
}
}
} else {
skip = true; // home page and never has or tried to login
}
}
if (appState && appState.user && !skip) {
fetch(url, params)
.then(response => {
if (!response.ok) {
throw new Error(response.status.toString());
} else {
return response.json() as Promise<User>;
}
})
.then(data => {
dispatch({ type: 'RECEIVE_USER', user: data, loginError: "" });
})
.catch(error => {
dispatch({ type: 'RECEIVE_USER', user: {} as User, loginError: error.message });
});
dispatch({ type: 'REQUEST_USER' });
}
}
};
const unloadedState: UserState = {
isLoading: false,
loginError: "",
loggedIn: false,
user: {
username: "",
email: "",
firstName: "",
lastName: "",
token: ""
}
};
export const reducer: Reducer<UserState> = (state: UserState | undefined, incomingAction: Action): UserState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case 'REQUEST_USER':
return {
user: state.user,
loginError: "",
loggedIn: false,
isLoading: true,
};
case 'RECEIVE_USER':
var loggedIn = false;
if (!action.loginError) {
loggedIn = true;
Cookies.setCookie("user", JSON.stringify(action.user), 7);
} else {
loggedIn = false;
Cookies.eraseCookie("user");
}
return {
user: action.user,
loginError: action.loginError,
loggedIn: loggedIn,
isLoading: false
};
break;
}
return state;
};
App.tsx
import * as React from 'react';
import { Route } from 'react-router';
import Layout from './components/Layout';
import Home from './components/Home';
import Counter from './components/Counter';
import FetchData from './components/FetchData';
import CapacityGrid from './components/CapacityGrid';
import { connect } from 'react-redux';
import { Container } from 'reactstrap';
import { ApplicationState } from './store';
import * as AppSettingsStore from './store/AppSettings';
import * as UserStore from './store/User';
import './css/custom.css'
import { userInfo } from 'os';
type AppSettingsProps =
AppSettingsStore.AppSettingsState
& typeof AppSettingsStore.actionCreators
type UserProps =
UserStore.UserState
& typeof UserStore.actionCreators
class App extends React.PureComponent<UserProps & AppSettingsProps & { children?: React.ReactNode }> {
public componentDidMount() {
this.fetchAppSettings();
this.fetchUser();
}
private fetchAppSettings() {
this.props.requestAppSettings();
}
private fetchUser() {
this.props.requestUser();
}
public render() {
return (
<React.Fragment>
{!this.props.isLoading &&
<Layout>
<Route path='/capacity-grid' component={CapacityGrid} />
<Route exact path='/' component={Home} />
<Route path='/counter' component={Counter} />
<Route path='/fetch-data/:startDateIndex?' component={FetchData} />
</Layout>
}
</React.Fragment>
)
}
}
/* works
export default connect(
(state: ApplicationState) => state.user,
UserStore.actionCreators
)(App as any); */
/*const mapDispatchToProps = (dispatch: any) => ({
userActions: () => dispatch(UserStore.actionCreators.requestUser),
appActions: () => dispatch(AppSettingsStore.actionCreators.requestAppSettings),
});*/
const mapStateToProps = (state: ApplicationState) => ({
user: state.user,
appSettings: state.appSettings
});
const mapDispatchToProps = (
UserStore.actionCreators,
AppSettingsStore.actionCreators
);
export default connect(mapStateToProps, mapDispatchToProps)(App as any);
【问题讨论】:
标签: javascript reactjs typescript redux