React Redux action вызывается перед init - PullRequest
0 голосов
/ 22 февраля 2020

Я довольно новичок в Redux и в целом в Redux-Saga и хотел использовать React-Boilerplate , чтобы попробовать небольшой проект, который в основном просто вызывает API и перебирает данные. И в настоящее время у меня есть проблема, в которой я застрял в течение нескольких часов. Может быть, у вас есть идея?

Мой React Component выглядит следующим образом:

import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';

import { useInjectSaga } from 'utils/injectSaga';
import { useInjectReducer } from 'utils/injectReducer';
import { 
  makeSelectDevices, 
  makeSelectLoading, 
  makeSelectError 
} from './selectors';
import reducer from './reducer';
import { fetchDevices } from './actions';
import saga from './saga';

export function LeafletMap(props) {
  const {devices, loading, error, fetchDevices } = props;

  useInjectReducer({ key: 'leafletMap', reducer });
  useInjectSaga({ key: 'leafletMap', saga });

  useEffect(() => {
    fetchDevices();
  }, [fetchDevices]);

  if (loading) return(<div>Loading...</div>)
  return (
    <div>
      { !error ? 
        <Map center={[47.3, 9.9]} zoom={9} style={{height: '500px'}}>
          <TileLayer 
              url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' 
              attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
          />
            { devices && devices.map((device)=> {
                let coordinates = [device.latitude, device.longitude];
                return (
                  <Marker key={device.id} position={coordinates}></Marker>
                ); 
            })}
        </Map>
        : ''
      }
    </div>
  );
};

LeafletMap.propTypes = {
  devices: PropTypes.array,
  loading: PropTypes.bool,
  error: PropTypes.any,
};

const mapStateToProps = createStructuredSelector({
  devices: makeSelectDevices(),
  loading: makeSelectLoading(),
  error: makeSelectError(),
});

function mapDispatchToProps(dispatch) {
  return {
    fetchDevices: () => dispatch(fetchDevices())
  };
}

const withConnect = connect(
  mapStateToProps,
  mapDispatchToProps,
);

export default compose(withConnect)(LeafletMap);

Когда мой компонент монтируется, я использую useEffect Hook для отправки действия, которое я связал со своими реквизитами, используя mapDispatchToProps. Файл действий выглядит следующим образом:

import { 
  FETCH_DATA, 
  FETCH_DATA_ERROR, 
  FETCH_DATA_SUCCESS,
  CLICK_DEVICE
} from './constants';

export function fetchDevices() {
  return {
    type: FETCH_DATA,
  };
}

export function fetchDevicesSuccess(devices) {
  return {
    type: FETCH_DATA_SUCCESS,
    devices
  };
}

export function fetchDevicesError(error) {
  return {
    type: FETCH_DATA_ERROR,
    error
  };
}

Моя сага затем реагирует на действие FETCH_DATA и вызывает генератор для получения данных из моего локального API:

import { all, call, put, takeEvery } from 'redux-saga/effects';
import request from 'utils/request';
import { fetchDevicesSuccess, fetchDevicesError } from './actions';
import { FETCH_DATA } from './constants';

function* fetchDevicesAsync() {
  yield takeEvery(FETCH_DATA, fetchAllDevices);
}

function* fetchAllDevices() {
  try {
    const requestUrl = '/api/devices';
    const devices = yield call(request, requestUrl);

    yield put(fetchDevicesSuccess(devices));
  } catch (error) {
    yield put(fetchDevicesError(error.toString()));    
  }
}

export default function* rootSaga() {
  yield all([fetchDevicesAsync()]);
}

Это в return должен вызывать мой редуктор, который выглядит следующим образом:

import produce from 'immer';
import { 
  FETCH_DATA, 
  FETCH_DATA_ERROR, 
  FETCH_DATA_SUCCESS,
} from './constants';
export const initialState = {
  devices: [],
  loading: true,
  error: false,
};

/* eslint-disable default-case, no-param-reassign */
const leafletMapReducer = (state = initialState, action) =>
  produce(state, () => {
    switch (action.type) {
      case FETCH_DATA:
        state.loading = true;
        state.error = false;
        break;
      case FETCH_DATA_ERROR:
        state.loading = false
        state.error = action.error;
        break;
      case FETCH_DATA_SUCCESS:
        state.loading = false;
        state.error = false;
        state.devices = action.devices;
        break;
    }
  });

export default leafletMapReducer;

Моя проблема здесь в том, что все работает, но мое действие не отображается в Redux DevTools, и мой компонент не обновляется после первоначального рендеринга. Кажется, что действие отправляется до события @@ INIT.

action is missing but data is in the store

Есть идеи, почему это происходит?

Заранее спасибо!

РЕДАКТИРОВАТЬ:

На всякий случай, это как-то связано с моими селекторами:

import { createSelector } from 'reselect';
import { initialState } from './reducer';

/**
 * Direct selector to the leafletMap state domain
 */

const selectLeafletMapDomain = state => state.leafletMap || initialState;

/**
 * Other specific selectors
 */

const makeSelectDevices = () =>
  createSelector(
    selectLeafletMapDomain,
    leafletMapState => leafletMapState.devices
  ); 

const makeSelectLoading = () =>
  createSelector(
    selectLeafletMapDomain,
    leafletMapState => leafletMapState.loading,
  );

const makeSelectError = () =>
  createSelector(
    selectLeafletMapDomain,
    leafletMapState => leafletMapState.error,
  );

/**
 * Default selector used by LeafletMap
 */

const makeSelectLeafletMap = () =>
  createSelector(selectLeafletMapDomain, leafletMapState => leafletMapState.toJS());

export default makeSelectLeafletMap;
export { 
  selectLeafletMapDomain, 
  makeSelectDevices, 
  makeSelectLoading, 
  makeSelectError
};

1 Ответ

0 голосов
/ 22 февраля 2020

Сам нашел проблему :) Проблема была в моем редукторе:

const leafletMapReducer = (state = initialState, action) =>
  produce(state, () => {             // <-- here
    switch (action.type) {
      case FETCH_DATA:
        state.loading = true;
        state.error = false;
        break;

Я ошибочно мутировал мое состояние, что приводит к ошибке. Правильное решение:

const leafletMapReducer = (state = initialState, action) =>
  produce(state, draftState => {     // use draftState instead of normal state
    switch (action.type) {
      case FETCH_DATA:
        draftState.loading = true;   //<------
        draftState.error = false;    //<------
        break;
...