我正在使用React和Redux开发日历应用程序。 日历应用从Google api获取日期及其事件,因此用户可以查看其Google日历。 在调试器中,我可以看到从分派到渲染功能的所有数据都正常。唯一的问题是(MonthlyCalendar)内部获取事件的组件得到了一个空对象。 当不使用redux而只是反应状态时,组件会使用更新的event对象获得新的道具。 我在组件的每个生命周期中添加了console.log。
使用常规状态(不是redux):
我不知道redux流缺少什么,但是在调试时显示渲染是使用更新的状态事件执行的, 仍然console.log打印为空。
从调度到渲染的代码:
发送
useEffect(() => {
window.addEventListener(
'message',
e => {
if (e.data && e.data.data) {
dispatch(connectBtnClicked(e.data.data));
}
},
false,
);
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
}, [])
操作
export const connectBtnClicked = (userName: string) => (dispatch: any) => {
const urlArray = [
'https://calendar-server.codev.co.il/getEvents',
'https://calendar-server.codev.co.il/getCalendarsListIds',
'https://calendar-server.codev.co.il/getSettings',
];
const requestsArray = urlArray.map(url => {
const request = new Request(url, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
method: 'GET',
});
return fetch(request).then(res => res.json());
});
Promise.all(requestsArray).then(allResults => {
console.log('***allResults', allResults);
dispatch({
type: CONNECT_BTN_CLICKED,
payload: {
isConnect: true,
userName,
dates: allResults[0].eventsByDates,
calendarsList: allResults[1].calendarsList,
},
});
});
};
减速器
import {
CONNECT_BTN_CLICKED,
SET_COMP_ID,
DISCONNECT_BTN_CLICKED,
CONNECT_ERROR,
} from '../actions/actionType';
const initState = {
isConnect: false,
isDisconnect: false,
isSettingsLoaded: false,
userName: '',
isLoader: false,
connectError: false,
statusCode: '',
dates: {},
calendarsList: [],
};
export default (state = initState, action: any) => {
console.log('setReducer', action);
switch (action.type) {
case SET_COMP_ID:
return {
...state,
compId: action.payload,
};
case CONNECT_ERROR:
return {
...state,
connectEtsyError: action.payload.err,
statusCode: action.payload.statusCode,
};
case CONNECT_BTN_CLICKED:
return {
...state,
isConnect: action.payload.isConnect,
userName: action.payload.userName,
dates: action.payload.dates,
calendarsList: action.payload.calendarsList,
isSettingsLoaded: true,
};
case DISCONNECT_BTN_CLICKED:
return {
...state,
isConnect: false,
// isDisconnect: action.payload,
dates: {},
calendarsList: [],
userName: '',
};
default:
return state;
}
};
Root Reducer
import { combineReducers } from 'redux';
//@ts-ignore
import settings from './settingsReducer';
const rootReducer = combineReducers({
settings,
});
export default rootReducer;
商店
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from '../reducers/rootReducer';
import thunk from 'redux-thunk';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
提供者包装App.js组件
import { BrowserRouter as Router } from 'react-router-dom';
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
import { I18nextProvider } from 'react-i18next';
import App from './components/App/App';
// import App from './components/App';
import i18n from './i18n';
import { Provider } from 'react-redux';
import store from './components/settings/store';
const locale = window.__LOCALE__;
const baseURL = window.__BASEURL__;
fedopsLogger.appLoaded();
ReactDOM.render(
<Provider store={store}>
<React.Suspense fallback={<div>Please wait...</div>}>
<Router>
{/* <ExperimentsProvider options={{ experiments }}> */}
<App />
{/* </ExperimentsProvider> */}
</Router>
</React.Suspense>
</Provider>,
document.getElementById('root'),
);
App.js渲染-MonthlyCalendar是事件接收组件
render() {
const { t } = this.props;
const events = this.props.events;
return (
<Switch>
<Route
path="/index"
render={() => (
<MonthlyCalendar
weekStarter={this.state.weekStarter}
events={events}
handleMonthChange={handleMonthChange}
isTimeZoneShown={this.state.isTimeZoneShown}
isTimeShown={this.state.isTimeShown}
locale={this.state.locale}
//timeZone={this.state.timeZone}
isTodayButtonStyleSeconday={this.state.isTodayButtonStyleSeconday}
/>
)}
></Route>
<Route
path="/settings"
render={() => (
<Settings
fetchEvents={this.fetchEvents}
initialState={"this.props.initialState"}
/>
)}
></Route>
<Route
path="/mobile"
render={() => (
<MonthlyCalendar
weekStarter={this.state.weekStarter}
events={events}
handleMonthChange={handleMonthChange}
isTimeZoneShown={this.state.isTimeZoneShown}
isTimeShown={this.state.isTimeShown}
locale={this.state.locale}
isTodayButtonStyleSeconday={this.state.isTodayButtonStyleSeconday}
/>
)}
></Route>
</Switch>
);
}
}
const mapDispatchToProps = (dispatch: any) => ({});
const mapStateToProps = (state: any) => ({
isConnect: state.settings.isConnect,
userName: state.settings.userName,
events: state.settings.dates,
calendarsList: state.settings.calendarsList,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(withTranslation()(withEnhancedStyleLoader(App)));
componentDidMount中没有setState或App.js中的任何其他方法都将其删除,因此只有道具更改可以重新呈现
编辑1 我放下了所有HOCS并仍在渲染中,我看到了更新的事件,但是子组件打印了空事件。
呈现收到的事件
编辑2
子组件仅渲染一次。即使事件道具已更新,并且重新渲染MonthlyCalendar(子组件)也不会重新渲染。我尝试过:
const events = {...this.props.events};
还:
const events = JSON.parse(JSON.stringfy(this.props.events);
没有工作...
编辑3-MonthlyCalendar组件
constructor(props: any) {
super(props);
console.log('[constructor] props.events: ',props.events)
const timezone = moment.tz.guess();
const dateObject = moment().tz(timezone, true);
this.state = {
dateObject,
timezone,
isTimezonesOpen: false,
};
}
shouldComponentUpdate(nextProps, nextState) {
console.log('[shouldComponentUpdate] props.events: ',this.props.events)
return true
}
....
getCalendar() {
const { events } = this.props;
const { dateObject } = this.state;
const beforeFillers = this.getMonthBeforFillers(dateObject, events);
const days = this.getDays(dateObject, events);
const afterFillers = this.hasAfterFillers(beforeFillers, days) ?
this.getAfterMonthFillers(dateObject, events) : {};
return { days, beforeFillers, afterFillers };
}
async componentDidUpdate(prevProps) {
console.log('[componentDidUpdate] props.events: ',this.props.events)
this.props.locale !== prevProps.locale && await this.updateLocale();
}
updateLocale = async () => {
const { locale, i18n } = this.props;
await i18n.changeLanguage(locale);
moment.locale(locale);
const { timezone, dateObject } = this.state;
const dateObjectToSet = moment(dateObject.format()).tz(timezone, true);
this.setState({ dateObject: dateObjectToSet });
}
async componentDidMount() {
console.log('[componentDidMount] props.events: ',this.props.events)
this.props.locale !== 'en' && await this.updateLocale();
}
render() {
const { t, weekStarter, isTodayButtonStyleSeconday, isTimeZoneShown, isTimeShown } = this.props;
const { dateObject, timezone, isTimezonesOpen } = this.state;
const { days, beforeFillers, afterFillers } = this.getCalendar();
const month = dateObject.format(t('Google_Calendar_Picker_Month'));
const timezoneSelected = moment().tz(timezone).format(t('Google_Calendar_Timezone_Selected'));
const timezoneSelectedTitle = t('Google_Calendar_Timezone_Selected_Title', { timezoneSelected });
console.log('[render] props.events: ',this.props.events)
return (
<TPAComponentsProvider value={{ mobile: false, rtl: false }}>
<div className={classes.MonthlyCalendar}>
<CalendarControllers
isTodayButtonStyleSeconday={isTodayButtonStyleSeconday}
todayClicked={this.todayClickedHander}
onPreviousClicked={() => this.timePickerClickedHandler(false)}
timeToDisplay={month}
onNextClicked={() => this.timePickerClickedHandler(true)}
onTimezoneChange={this.timezoneChangeHandler}
timezone={timezoneSelectedTitle}
isTimezonesOpen={isTimezonesOpen}
openTimezones={this.openTimezones}
closeTimezones={this.closeTimezones}
isTimeZoneShown={isTimeZoneShown}
/>
<MonthTable
weekStarter={weekStarter}
days={days}
beforeFillers={beforeFillers}
dateObject={dateObject}
afterFillers={afterFillers}
renderCell={(
time: any,
events: any,
cellRef: any,
handleEventClick: any,
setExpendedEvent: any,
expendedEvent: any,
isOutsideClicked: any,
) => (
<MonthlyCell
events={events}
handleEventClick={handleEventClick}
time={time}
cellRef={cellRef}
expendedEvent={expendedEvent}
isOutsideClicked={isOutsideClicked}
setExpendedEvent={setExpendedEvent}
isTimeShown={isTimeShown}
/>
)}
/>
</div>
</TPAComponentsProvider>
);
}
}
export default withTranslation()(MonthlyCalendar);
编辑4
在寻找解决方案之后,我将密钥添加到MonthlyCalendar的div中,并且还添加了析构函数{... this.props.events}。仍然没有重新渲染 以下是更新的MonthlyCalendar:
getCalendar() {
const mutableEvents = {...this.props.events};
const { dateObject } = this.state;
const beforeFillers = this.getMonthBeforFillers(dateObject, mutableEvents);
const days = this.getDays(dateObject, mutableEvents);
const afterFillers = this.hasAfterFillers(beforeFillers, days) ?
this.getAfterMonthFillers(dateObject, mutableEvents) : {};
return { days, beforeFillers, afterFillers };
}
async componentDidUpdate(prevProps) {
console.log('[componentDidUpdate] props.events: ',this.props.events)
this.props.locale !== prevProps.locale && await this.updateLocale();
}
updateLocale = async () => {
const { locale, i18n } = this.props;
await i18n.changeLanguage(locale);
moment.locale(locale);
const { timezone, dateObject } = this.state;
const dateObjectToSet = moment(dateObject.format()).tz(timezone, true);
this.setState({ dateObject: dateObjectToSet });
}
async componentDidMount() {
console.log('[componentDidMount] props.events: ',this.props.events)
this.props.locale !== 'en' && await this.updateLocale();
}
render() {
const { t, weekStarter, isTodayButtonStyleSeconday, isTimeZoneShown, isTimeShown, events: propEvents } = this.props;
const eventsKey = Object.keys(propEvents).length;
const { dateObject, timezone, isTimezonesOpen } = this.state;
const { days, beforeFillers, afterFillers } = this.getCalendar();
const month = dateObject.format(t('Google_Calendar_Picker_Month'));
const timezoneSelected = moment().tz(timezone).format(t('Google_Calendar_Timezone_Selected'));
const timezoneSelectedTitle = t('Google_Calendar_Timezone_Selected_Title', { timezoneSelected });
console.log('[render] props.events: ',this.props.events)
return (
<TPAComponentsProvider value={{ mobile: false, rtl: false }}>
<div key={eventsKey} className={classes.MonthlyCalendar}>
<CalendarControllers
isTodayButtonStyleSeconday={isTodayButtonStyleSeconday}
todayClicked={this.todayClickedHander}
onPreviousClicked={() => this.timePickerClickedHandler(false)}
timeToDisplay={month}
onNextClicked={() => this.timePickerClickedHandler(true)}
onTimezoneChange={this.timezoneChangeHandler}
timezone={timezoneSelectedTitle}
isTimezonesOpen={isTimezonesOpen}
openTimezones={this.openTimezones}
closeTimezones={this.closeTimezones}
isTimeZoneShown={isTimeZoneShown}
/>
<MonthTable
weekStarter={weekStarter}
days={days}
beforeFillers={beforeFillers}
dateObject={dateObject}
afterFillers={afterFillers}
renderCell={(
time: any,
events: any,
cellRef: any,
handleEventClick: any,
setExpendedEvent: any,
expendedEvent: any,
isOutsideClicked: any,
) => (
<MonthlyCell
events={events}
handleEventClick={handleEventClick}
time={time}
cellRef={cellRef}
expendedEvent={expendedEvent}
isOutsideClicked={isOutsideClicked}
setExpendedEvent={setExpendedEvent}
isTimeShown={isTimeShown}
/>
)}
/>
</div>
</TPAComponentsProvider>
);
}
}
export default withTranslation()(MonthlyCalendar);