Использование состояния редуктора внутри useEffect - PullRequest
5 голосов
/ 09 октября 2019

Hello All ?? У меня есть вопрос о нашем любимом API Hooks!

Что я пытаюсь сделать?

Я пытаюсь получить фотографии из какой-то удаленной системы. Я сохраняю URL-адреса больших двоичных объектов для этих фотографий в своем состоянии редуктора с идентификатором.

У меня есть вспомогательная функция, завернутая в запомненную версию, возвращаемую хуком useCallback. Эта функция вызывается в useEffect, который я определил.

Проблема ⚠️

Мой обратный вызов, или вспомогательная функция, зависит от части состояния редуктора. Который обновляется каждый раз при получении фотографии. Это заставляет компонент снова запускать эффект в useEffect и, таким образом, вызывать бесконечный цикл.

component renders --> useEffect runs ---> `fetchPhotos` runs --> after 1st photo, reducer state is updated --> component updates because `useSelector`'s value changes ---> runs `fetchPhotos` again ---> infinite
const FormViewerContainer = (props) => {
  const { completedForm, classes } = props;

  const [error, setError] = useState(null);

  const dispatch = useDispatch();
  const photosState = useSelector(state => state.root.photos);

  // helper function which fetches photos and updates the reducer state by dispatching actions
  const fetchFormPhotos = React.useCallback(async () => {
    try {
      if (!completedForm) return;
      const { photos: reducerPhotos, loadingPhotoIds } = photosState;
      const { photos: completedFormPhotos } = completedForm;
      const photoIds = Object.keys(completedFormPhotos || {});

      // only fetch photos which aren't in reducer state yet
      const photoIdsToFetch = photoIds.filter((pId) => {
        const photo = reducerPhotos[pId] || {};
        return !loadingPhotoIds.includes(pId) && !photo.blobUrl;
      });

      dispatch({
        type: SET_LOADING_PHOTO_IDS,
        payload: { photoIds: photoIdsToFetch } });

      if (photoIdsToFetch.length <= 0) {
        return;
      }

      photoIdsToFetch.forEach(async (photoId) => {
        if (loadingPhotoIds.includes(photoIds)) return;

        dispatch(fetchCompletedFormPhoto({ photoId }));
        const thumbnailSize = {
          width: 300,
          height: 300,
        };

        const response = await fetchCompletedFormImages(
          cformid,
          fileId,
          thumbnailSize,
        )

        if (response.status !== 200) {
          dispatch(fetchCompletedFormPhotoRollback({ photoId }));
          return;
        }

        const blob = await response.blob();
        const blobUrl = URL.createObjectURL(blob);

        dispatch(fetchCompletedFormPhotoSuccess({
          photoId,
          blobUrl,
        }));
      });
    } catch (err) {
      setError('Error fetching photos. Please try again.');
    }
  }, [completedForm, dispatch, photosState]);

  // call the fetch form photos function
  useEffect(() => {
    fetchFormPhotos();
  }, [fetchFormPhotos]);

  ...
  ...
}

Что я пробовал?

Я нашел альтернативный способ полученияфотографии, иначе говоря, отправляя действие и используя рабочую сагу, чтобы сделать всю выборку. Это устраняет всю необходимость в помощнике в компоненте и, следовательно, не useCallback и, следовательно, никаких повторных визуализаций. В этом случае useEffect зависит только от dispatch, что нормально.

Вопрос?

Я борюсь с умственным модальным использованием API-интерфейса hooks. Я вижу очевидную проблему, но я не уверен, как это можно сделать без использования промежуточных программ-редукторов, таких как thunks и sagas.

Редактировать:

Функция редуктора:

export const initialState = {
  photos: {},
  loadingPhotoIds: [],
};

export default function photosReducer(state = initialState, action) {
  const { type, payload } = action;
  switch (type) {
    case FETCH_COMPLETED_FORM_PHOTO: {
      return {
        ...state,
        photos: {
          ...state.photos,
          [payload.photoId]: {
            blobUrl: null,
            error: false,
          },
        },
      };
    }
    case FETCH_COMPLETED_FORM_PHOTO_SUCCESS: {
      return {
        ...state,
        photos: {
          ...state.photos,
          [payload.photoId]: {
            blobUrl: payload.blobUrl,
            error: false,
          },
        },
        loadingPhotoIds: state.loadingPhotoIds.filter(
          photoId => photoId !== payload.photoId,
        ),
      };
    }
    case FETCH_COMPLETED_FORM_PHOTO_ROLLBACK: {
      return {
        ...state,
        photos: {
          ...state.photos,
          [payload.photoId]: {
            blobUrl: null,
            error: true,
          },
        },
        loadingPhotoIds: state.loadingPhotoIds.filter(
          photoId => photoId !== payload.photoId,
        ),
      };
    }
    case SET_LOADING_PHOTO_IDS: {
      return {
        ...state,
        loadingPhotoIds: payload.photoIds || [],
      };
    }
    default:
      return state;
  }
}

1 Ответ

1 голос
/ 09 октября 2019

Вы можете включить логику вычисления photoIdsToFetch в функцию выбора, чтобы уменьшить количество визуализаций, вызванных изменением состояния.

const photoIdsToFetch = useSelector(state => {
    const { photos: reducerPhotos, loadingPhotoIds } = state.root.photos;
    const { photos: completedFormPhotos } = completedForm;
    const photoIds = Object.keys(completedFormPhotos || {});
    const photoIdsToFetch = photoIds.filter(pId => {
      const photo = reducerPhotos[pId] || {};
      return !loadingPhotoIds.includes(pId) && !photo.blobUrl;
    });
    return photoIdsToFetch;
  },
  equals
);

Однако функция выбора не запоминается, она возвращает новыймассив объектов каждый раз, таким образом, равенство объектов здесь не будет работать. Вам нужно будет предоставить метод isEqual в качестве второго параметра (который будет сравнивать два массива на равенство значений), чтобы селектор возвращал один и тот же объект, когда идентификаторы идентичны. Вы можете написать свою собственную или deep-equals библиотеку, например:

import equal from 'deep-equal';

fetchFormPhotos будет зависеть только от [photoIdsToFetch, dispatch] таким образом.

Я не уверен, как ваш редукторфункции изменяют состояние, поэтому это может потребовать некоторой тонкой настройки. Идея такова: выбирайте только состояние магазина, от которого вы зависите, чтобы другие части магазина не вызывали повторные рендеры.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...