React-Redux-状态对象更新,重新渲染组件,但更改不会传递给子组件

时间:2020-07-13 09:40:38

标签: javascript reactjs redux react-redux

我正在使用React和Redux开发日历应用程序。 日历应用从Google api获取日期及其事件,因此用户可以查看其Google日历。 在调试器中,我可以看到从分派到渲染功能的所有数据都正常。唯一的问题是(MonthlyCalendar)内部获取事件的组件得到了一个空对象。 当不使用redux而只是反应状态时,组件会使用更新的event对象获得新的道具。 我在组件的每个生命周期中添加了console.log。

使用redux时,我得到以下信息:enter image description here

使用常规状态(不是redux):

enter image description here

我不知道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并仍在渲染中,我看到了更新的事件,但是子组件打印了空事件。

呈现收到的事件

enter image description here

编辑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);

0 个答案:

没有答案