Skip to main content

Thunks

Асинхронный экшен

Thunk. Effect. В них можно обрабатывать ответы с сервера и обогащать данные дополнительными полями.

dispatch indise component --> thunk --> get API data --> modified data --> save data to store

import axios from 'axios';
import { TUser } from './users.types';
import { Dispatch } from '@reduxjs/toolkit';
import { usersSlice } from './users.slice';

const { usersLoading, usersSuccess, usersError, resetUsersState } =
usersSlice.actions;

export const fetchUsersThunk = (limit: number) => async (dispatch: Dispatch) => {
dispatch(resetUsersState());
dispatch(usersLoading(true));

try {
// response typing
const response = await axios.get<TUser[]>(
`https://jsonplaceholder.typicode.com/users?limit=${limit}`,
);

// if success
setTimeout(() => {
dispatch(usersLoading(false));
dispatch(usersSuccess(response.data));
}, 1000);

// if error
} catch (e) {
dispatch(usersLoading(false));
dispatch(usersError(`${e}`));
}
};

// или экспортом в объекте
export const usersThunks = {
fetchUsersThunk,
};

Использование в компоненте

import { useDispatch, useSelector } from 'react-redux';
import { fetchUsersThunk } from '@store/users';

const SomeComp = () => {
const dispatch = useDispatch();
const users = useSelector((state: AppState) => state.users);

useEffect(() => {
dispatch(fetchUsersThunk(10));
}, []);

const { isFetching, error, userList } = users;

// render users
...
};

export default SomeComp;

Пример thunk

import { Dispatch } from 'redux';
import { store } from '../store/store';
import routesService from '../api/services/routes-service';
import { setLoadingAction } from '../slices/orders';

export const attachReturnsToPickups = (routeIds: number[]) => {
return async function (dispatch: Dispatch<any>) {
dispatch(setLoadingAction(true));

const data = await routesService.attachReturnsToPickups(routeIds);
if (data) {
// доступ к стору
const filters = store.getState().orders.filters;
store.dispatch(getAllRoutes());
store.dispatch(getAllOrders());
dispatch(messageAction(messageHandler('Партнёрские возвраты успешно обработаны', MessageType.SUCCESS)));
} else {
dispatch(setLoadingAction(false));
dispatch(messageAction(messageHandler('Не удалось обработать партнёрские возвраты')));
}
};
};

Примеры синхронных экшенов

// обновление данных в массиве
setDrugstoreTypeFilter(state, { payload }: PayloadAction<DrugstoreType>) {
const { drugstoreType } = state.filter;

const isInclude = drugstoreType.includes(payload);

if (isInclude) {
state.filter.drugstoreType = drugstoreType.filter(
type => type !== payload,
);
} else {
state.filter.drugstoreType = [...drugstoreType, payload];
}
},

// обновление данных в массиве, если payload тоже массив
setOwnDrugstoreTypeFilter(state, { payload }: PayloadAction<PickUpType[]>) {
const { ownDrugstoreType } = state.filter;

payload.map(type => {
const isExist = ownDrugstoreType.some(
currentType => currentType === type,
);

if (isExist) {
// удалить фильтр
const index = ownDrugstoreType.indexOf(type);
ownDrugstoreType.splice(index, 1);
} else {
// добавить фильтр
ownDrugstoreType.push(type);
}
});
},

Утилиты

import { PayloadAction } from '@reduxjs/toolkit';
export type ObjectType = Record<string, unknown>;

// simpleMerge
export const simpleMerge = <State, Payload>(state: State, action: PayloadAction<Payload>): State => ({
...state,
...action.payload,
});

// simpleMergeThunk
export const simpleMergeThunk = <State, Payload>() => (state: State, action: PayloadAction<Payload>): State => ({
...state,
...action.payload,
});

// createPayload
export const createPayload = <Payload extends ObjectType>(payload: Payload) => ({
payload: payload,
});

// createSimpleReducer
export const createSimpleReducer = <State, K extends keyof State>(key: K) => (
state: State,
action: PayloadAction<State[K]>
) => ({
...state,
[key]: action.payload,
});

// createSimpleDraftReducer
export const createSimpleDraftReducer = <State, K extends keyof State>(key: K) => (
state: State,
action: PayloadAction<State[K]>
) => {
state[key] = action.payload;
};

// createMergeReducer
export const createMergeReducer = <State>() => simpleMergeThunk<State, Partial<State>>();

// createMergeDraftReducer
export const createMergeDraftReducer = <State, K extends keyof State>(key: K) => (
state: State,
action: PayloadAction<State[K]>
) => {
state[key] = {
...state[key],
...action.payload,
};
};

Обработка ответа после dispatch

  const syncGoTemplatesHandler = async () => {
const result = (await dispatch(syncGoTemplates())) as unknown as boolean;

if (result) {
setSuccess('Успешно');
} else {
setSuccess('Произошла ошибка');
}
};

Обработка ответа после dispatch 2

// thunk, который возвращает данные
export const setSlaInterval = (slaId: number, intervalData: ISlaInterval) => {
return async function (dispatch: Dispatch<any>): Promise<FormError[] | ISlaInterval | undefined> {
dispatch(setLoadingAction(true));
const response = !intervalData.id
? await slaService.createInterval(slaId, intervalData)
: await slaService.editInterval(slaId, intervalData);
if (response.success) {
await dispatch(setLoadingAction(false));
return response.data;
} else {
await dispatch(
setMessageAction(messageHandler('Возникла ошибка при попытке сохранить интервал', MessageType.ERROR))
);
await dispatch(setLoadingAction(false));
if (response.errors) {
return response.errors;
}
}
};
};

...

// экшен с обработкой ответа внутри компонента
const saveInterval = async (
slaInWork: ISla,
intervalInWork: ISlaInterval,
setSlaInWork: (data: ISla) => void,
originalSla: ISla,
setOriginalSla: (data: ISla) => void
) => {
const responseInterval = (await dispatch(setSlaInterval(slaInWork.id, intervalInWork))) as unknown as
| FormError[]
| ISlaInterval
| undefined;

if (responseInterval) {
const data = responseInterval as ISlaInterval;

if (data.id) {
const iId = intervalInWork.id;

setSlaInWork({
...slaInWork,
intervals: slaInWork.intervals.map((interval) => {
return interval.uniqId !== intervalInWork.uniqId
? interval
: {
...data,
uniqId: intervalInWork.uniqId,
};
}),
});

setOriginalSla({
...originalSla,
intervals: !iId
? [
...originalSla.intervals,
{
...data,
uniqId: intervalInWork.uniqId,
},
]
: [
...originalSla.intervals.map((interval) => {
return interval.uniqId !== intervalInWork.uniqId
? interval
: {
...data,
uniqId: intervalInWork.uniqId,
};
}),
],
});

setErrors([]);
}

if (Array.isArray(responseInterval)) {
setErrors(responseInterval);
}
}
};

Обработка ответа после dispatch 3

const handleSuspend = async () => {
const newErrors = (await dispatch(hubSuspend(time, search))) as unknown as FormError[];

if (!newErrors) {
close();
} else {
setErrors(newErrors || []);
}
};