Saga to Listeners

A comprehensive guide for migrating to the new listeners architecture

highlighting key concepts and differences in implementation:

Setupping the Configs

// src/app/createAppSelector.ts
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from './store';

export const createAppSelector = createSelector.withTypes<RootState>();
// src/app/createAppSlice.ts
import {
    asyncThunkCreator,
    buildCreateSlice,
} from '@reduxjs/toolkit';

export const createAppSlice = buildCreateSlice({
    creators: { asyncThunk: asyncThunkCreator },
});
// src/app/listener.ts
import { createListenerMiddleware } from '@reduxjs/toolkit';
import type {
    AppDispatch,
    RootState,
} from './store';

export const listener = createListenerMiddleware();

/**
 * Function to start listening for actions
 */
export const startAppListening = listener.startListening.withTypes<
    RootState,
    AppDispatch
>();

Understanding the Migration

The main differences between Sagas and Listeners involve how we handle side effects and state management:

  • call → Direct function calls
  • putlistenerApi.dispatch
  • Saga generators → Listener effect callbacks
  • selectlistenerApi.getState

Code Comparison

/**
 * Get device from Glados
 */
export function* getDeviceSaga(): Generator<StrictEffect> {
    const hardwareService = GladosServiceFactory.getHardwareService();

    try {
        const response = 
            (yield call([hardwareService, hardwareService.getHardwareDetailGroup], 'v1', params)) as
                Awaited<ReturnType<typeof hardwareService.getHardwareDetailGroup>>;
        const { data } = response;

        yield put(getDeviceSuccess(data));
    }
    catch (error: any) {
        yield call([console, 'error'], error);

        const { response } = error;
        if (response && response.status === 404) {
            yield call(redirectToDop);
        } else {
            yield put(getDeviceFailed());
        }
    }
}

/**
 * Get device success saga
 */
export function* getDeviceSuccessSaga(): Generator<StrictEffect> {
    const atomicId = (yield select(selectAtomicId())) as SelectorReturnType<typeof selectAtomicId>;
    yield put(setAtomicId(atomicId!));
    yield put(getSubscriptions());
}

Key Concepts in Listeners

  1. Action Creation and Dispatch

    • Instead of using put effects like in Sagas, Listeners use listenerApi.dispatch
    • Actions are dispatched directly without needing to yield
  2. State Management

    • Replace select effects with listenerApi.getState()
    • Access state directly through the listener API
  3. Effect Handling

    • Replace generator functions with effect callbacks
    • Use startAppListening to register listeners
    • Handle success and error cases with separate listeners using matcher