Я работаю над приложением-календарем, используя React и Redux. Приложение календаря извлекает даты и их события из API Google, чтобы пользователи могли видеть свои календари Google. В отладчике я вижу, что данные в порядке от отправки до функции рендеринга. единственная проблема заключается в том, что компонент внутри (MonthlyCalendar), который получает события, получает пустой объект. когда не используется redux и вместо этого просто реагирует на состояние, компонент получает новые реквизиты с обновленным объектом событий. Я добавил console.log к каждому жизненному циклу компонента.
При использовании redux я получаю следующее:
using regular state (not redux):
i don't know what I'm missing with redux flow but when debugging it's shown that render is executed with the updated state events,
still the console.log print empty.
The code from dispatch to render :
Dispatch
useEffect(() => {
window.addEventListener(
'message',
e => {
if (e.data && e.data.data) {
dispatch(connectBtnClicked(e.data.data));
}
},
false,
);
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
}, [])
Action
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,
},
});
});
};
Reducer
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;
Store
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;
Provider Wrapping App.js Component
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(
Please wait...}>
{/* */}
{/* */}
,
document.getElementById('root'),
);
App.js render - MonthlyCalendar is the events receiving component
render() {
const { t } = this.props;
const events = this.props.events;
return (
(
)}
>
(
)}
>
(
)}
>
);
}
}
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)));
There is not setState in componentDidMount or any other method in App.js I removed it all, so only props change could re-render
Edit 1
i dropped all the HOCS and still in render i see the updated events, but the child component print empty events.
Render on events received
введите описание изображения здесь
EDIT 2
дочерний компонент отображается только один раз. даже когда свойства событий обновляются, и повторная визуализация MonthlyCalendar (дочерний компонент) не выполняется повторно. Я пробовал:
const events = {...this.props.events};
также:
const events = JSON.parse(JSON.stringfy(this.props.events);
не сработало ...
EDIT 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);
EDIT 4
после поиска решений я добавил ключ в div MonthlyCalendar, а также добавил деструктор {... 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);