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;
}
}