Thank you markerikson for providing an answer. This inspired me to create a solution with thunks. Please give me some feedback here :)
I will be using hooks and @reduxjs/toolkit in my example.
This is the state of my ConfirmationModal reducer:
export interface confirmationModalState {
isOpened: boolean;
isConfirmed: boolean;
isCancelled: boolean;
}
export const initialConfirmationModalState: confirmationModalState = {
isOpened: false,
isConfirmed: false,
isCancelled: false,
};
This is the slice (a combination of the reducer and actions):
import { createSlice } from '@reduxjs/toolkit';
import { initialConfirmationModalState } from './state';
const confirmationModalSlice = createSlice({
name: 'controls/confirmationModal',
initialState: initialConfirmationModalState,
reducers: {
open: state => {
state.isOpened = true;
state.isConfirmed = false;
state.isCancelled = false;
},
confirm: state => {
state.isConfirmed = true;
state.isOpened = false;
},
cancel: state => {
state.isCancelled = true;
state.isOpened = false;
},
},
});
export const confirmationModalActions = confirmationModalSlice.actions;
export default confirmationModalSlice;
This is the thunk action for it:
import { createAsyncThunk } from '@reduxjs/toolkit';
import ThunkApiConfig from '../../../types/ThunkApiConfig';
import { AppState } from '../../reducers';
import { confirmationModalActions } from './slice';
const confirmationModalThunkActions = {
open: createAsyncThunk<boolean, void, ThunkApiConfig>(
'controls/confirmationModal',
async (_, { extra, dispatch }) => {
const store = extra.store;
dispatch(confirmationModalActions.open());
return await new Promise<boolean>(resolve => {
store.subscribe(() => {
const state: AppState = store.getState();
if (state.controls.confirmationModal.isConfirmed) {
resolve(true);
}
if (state.controls.confirmationModal.isCancelled) {
resolve(false);
}
});
});
},
),
};
export default confirmationModalThunkActions;
You can notice it uses extra.store to perform the subscribe. We need to provide it when creating a store:
import combinedReducers from './reducers';
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import { ThunkExtraArguments } from '../types/ThunkExtraArguments';
function createStore() {
const thunkExtraArguments = {} as ThunkExtraArguments;
const customizedMiddleware = getDefaultMiddleware({
thunk: {
extraArgument: thunkExtraArguments,
},
});
const store = configureStore({
reducer: combinedReducers,
middleware: customizedMiddleware,
});
thunkExtraArguments.store = store;
return store;
}
export default createStore();
Now, let's create a hook that allows us to dispatch all of the above actions:
import { useDispatch, useSelector } from 'react-redux';
import { AppState } from '../../../reducers';
import { useCallback } from 'react';
import confirmationModalThunkActions from '../thunk';
import { confirmationModalActions } from '../slice';
import { AppDispatch } from '../../../../index';
export function useConfirmationModalState() {
const dispatch: AppDispatch = useDispatch();
const { isOpened } = useSelector((state: AppState) => ({
isOpened: state.controls.confirmationModal.isOpened,
}));
const open = useCallback(() => {
return dispatch(confirmationModalThunkActions.open());
}, [dispatch]);
const confirm = useCallback(() => {
dispatch(confirmationModalActions.confirm());
}, [dispatch]);
const cancel = useCallback(() => {
dispatch(confirmationModalActions.cancel());
}, [dispatch]);
return {
open,
confirm,
cancel,
isOpened,
};
}
(don't forget to attach confirm and cancel to the buttons in your modal)
And that's it! We can now dispatch our confirmation modal:
export function usePostControls() {
const { deleteCurrentPost } = usePostsManagement();
const { open } = useConfirmationModalState();
const handleDelete = async () => {
const { payload: isConfirmed } = await open();
if (isConfirmed) {
deleteCurrentPost();
}
};
return {
handleDelete,
};
}