在调度之间的路径“ ...”中检测到状态突变。这可能会导致错误的行为

时间:2019-04-08 09:51:54

标签: javascript reactjs redux

我的父组件中有一个列表和日历视图组件。对于日历组件,我希望能够将搜索过滤器推送到我的网址,以过滤出未选择的位置。我正在尝试根据提供给我的queryString函数的参数生成一个querystring,但是当我将queryString推送到url时,出现以下错误:

  

在调度之间的路径locations.calendarLocationList.0中检测到状态突变。这可能会导致错误的行为。 (http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments

我不确定是什么原因造成的,因为在此过程中我没有触摸状态。

父组件,渲染列表和日历视图

class LocationShell extends Component<
  LocationShellProps & WithNamespaces & RouteComponentProps,
  LocationShellState
  > {
  constructor(props: LocationShellProps & WithNamespaces & RouteComponentProps) {
    super(props);

    this.state = {
      isCalendarView: false,
      open: false,
      locationIdToDelete: -1,
      activePage: 1,
      activeSortHeader: undefined,
      direction: 'ascending',
      searchValue: undefined
    };
  }

  componentDidMount = (
    { loadLocations, loadSessionLocations, loadCalendarListLocations } = this.props,
    { activePage } = this.state
  ) => {
    loadLocations({ page: activePage });
    loadSessionLocations();
    loadCalendarListLocations();
  };

  toggleView = () => {
    const { isCalendarView } = this.state;
    this.setState((prevState) => ({
      ...prevState,
      isCalendarView: !isCalendarView
    }))
  }

  renderListView = () => {
    const { locationStatus, locations, selectedLocationId, history, match, pages, t } = this.props;
    const { activePage, activeSortHeader, direction } = this.state;

    switch (locationStatus) {
      case ProgressStatus.InProgress:
        return <InitialLoader />
      case ProgressStatus.Done:
        return (
          <DataTableWrapper
            // props
          />
        )
      case ProgressStatus.Error:
        return <NoDataFound />
      case ProgressStatus.Uninitialized:
        return null
    }
  }

  renderCalendarView = ({ calendarListStatus, sessionLocations, calendarListLocations } = this.props) => {
    switch (calendarListStatus) {
      case ProgressStatus.InProgress:
        return <InitialLoader />
      case ProgressStatus.Done:
        const events = toLocationEvents(sessionLocations!);
        return <CalendarView {...this.props} events={events} items={calendarListLocations!} name={'locations'} />
      case ProgressStatus.Error:
        return <NoDataFound />
      case ProgressStatus.Uninitialized:
        return null
    }
  }

  render() {
    const { pathName, t } = this.props;
    const { isCalendarView } = this.state;
    return (
      <Fragment>
        <PageHeader
          breadCrumbParts={split(pathName, '/').map(x => t(x))}
          title={t('moduleTitle')}
        />
        <Button.Group size="mini" style={{ padding: '10px 5px 10px 0px' }}>
          <Button positive={!isCalendarView} onClick={this.toggleView}>Lijst</Button>
          <Button.Or />
          <Button positive={isCalendarView} onClick={this.toggleView}>Kalender</Button>
        </Button.Group>
        <Button
          positive
          icon='add'
          size="mini"
          labelPosition='right'
          content="Nieuwe locatie"
          onClick={() => this.props.history.push(this.props.match.path + '/create')}
        />
        {isCalendarView ? this.renderCalendarView() : this.renderListView()}
      </Fragment>
    );
  }
}

const mapStateToProps = (state: GlobalState) => {
  return {
    locations: getLocations(state.locations),
    calendarListLocations: state.locations.calendarLocationList,
    calendarListStatus: state.locations.calendarListStatus,
    sessionLocations: state.locations.sessionLocations,
    selectedLocation: getSelectedLocation(state.locations),
    selectedLocationId: getSelectedLocationId(state.locations),
    pages: getAmountPages(state.locations),
    locationStatus: state.locations.locationStatus,
    sessionLocationStatus: state.locations.sessionLocationStatus,
    pathName: getPathName(state.router)
  };
};

const mapDispatchToProps = (dispatch: Dispatch) => ({
  loadLocations: (queryParams: QueryParams) =>
    dispatch(FetchLocations(queryParams)),
  loadSessionLocations: () => dispatch(FetchTrainingSessionLocations({})),
  loadCalendarListLocations : () => dispatch(FetchCalendarListLocations({})),
  clearLocations: () => dispatch(ClearLocations()),
  deleteLocation: (id: number) => dispatch(DeleteLocation({ locationId: id }))
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(withNamespaces('locations')(LocationShell));

renderCalendarView()正在渲染我的日历组件

我的日历组件:

interface CalendarViewState {
    selectedIds: number[];
}

type CalendarViewProps = {
    events: CalendarEvent[];
    name: string;
    items: CalendarListLocation[];
    navigatePush: (values: string) => void;
} & RouteComponentProps

class CalendarView extends Component<CalendarViewProps & WithNamespaces, CalendarViewState> {

    state: CalendarViewState = {
        selectedIds: []
    }

    componentDidMount = () => {
        const { events, items } = this.props;
        const { baseUrl, newEntity } = moduleConstants;
        this.setState((prevState) => ({
            ...prevState,
            selectedIds: items.map(x => x._id)
        }), () => {
            updateQueryString(this.props, { page: 1, locations: [1, 2] })
        }
        )
    }

    queryParams(props: CalendarViewProps = this.props) {
        return queryParams<QueryParams>(props.location.search);
    }
    componentDidUpdate = (prevProps: CalendarViewProps, prevState: CalendarViewState) => {
        const { selectedIds } = this.state;
        console.log()
        if (!isEqual(prevState.selectedIds, selectedIds)) {
            console.log(this.queryParams())
        }
    }
    handleChange = (id: number) => {
        const { selectedIds } = this.state;
        this.setState((prevState) => ({
            ...prevState,
            selectedIds: (selectedIds.includes(id) ? selectedIds.filter(x => x !== id) : [...selectedIds, id])
        }));
    };

    render() {
        const { events, name, t, items } = this.props
        return (
            <Grid divided="vertically" padded>
                <Grid.Row columns={2}>
                    <Grid.Column width={4}>
                        <CalendarSelectionList
                            name={t(name)}
                            onSelectionChange={this.handleChange}
                            selectedIds={this.state.selectedIds}
                            locations={items.sort((a: CalendarListLocation, b: CalendarListLocation) => a.name.localeCompare(b.name))}
                        />
                    </Grid.Column>
                    <Grid.Column width={12}>
                        <div style={{ height: '800px' }}>
                            <Calendar
                                events={events.filter(x => this.state.selectedIds.includes(x.id))}
                            />
                        </div>
                    </Grid.Column>
                </Grid.Row>
            </Grid>
        );
    }
}

const mapDispatchToProps = (dispatch: Dispatch) => ({
    navigatePush: (path: string) => dispatch(push(path))
});

export default connect(
    null,
    mapDispatchToProps
)(withNamespaces(['calendar'])(CalendarView));

updateQueryString(this.props, { page: 1, locations: [1, 2] })被解雇,此函数将使用生成的queryString更新网址

export function queryParams<T>(search: string) {
  return (queryString.parse(search) as unknown) as T;
}

export function updateQueryString<T>(props: RouteComponentProps, newValues: T) {
  const currentQuery = queryParams<T>(props.location.search);
  const newProps = Object.assign(currentQuery, newValues);
  props.history.push({
    pathname: props.location.pathname,
    search: queryString.stringify(filterSearchResults(newProps))
  });
}
function filterSearchResults(values: any) {
  let obj: any = {};
  Object.keys(values).forEach(
    key => values[key] && (obj[key] = values[key])
  );
  return obj;
}

此后,发生上述错误。为什么会发生此错误?

1 个答案:

答案 0 :(得分:0)

该错误表示locations.calendarLocationList已被更改,而Redux存储应该是不可变的。

calendarLocationListitems中用作CalendarView,并用items.sort(...)进行了变异,因为数组sort改变了现有的数组而不是创建一个新的数组。

可以用[...items].sort(...)来解决。