Advanced Redux — Side Effects, Async Tasks & ThunksEntry 3 of 9

A UI Slice for Notifications

A separate slice that stores UI state, such as a notification banner showing the status of an HTTP request.

Notes

  • HTTP status (pending, success, error) is app-wide UI state, so it fits in its own ui slice next to cart.
  • One showNotification action takes status, title, and message, and any component or thunk can dispatch it.
  • A Notification component reads state.ui.notification and renders when it's not null.

What / Why / When

What

Global UI feedback stored in Redux.

Why

Side-effect code (thunks, components) can report status without prop drilling.

When

Status messages, loading banners, toggles shared across the app.

Code compare

React
// store/ui-slice.tsimport { createSlice, type PayloadAction } from "@reduxjs/toolkit";​type Notification = {  status: "pending" | "success" | "error";  title: string;  message: string;};​type UiState = { cartIsVisible: boolean; notification: Notification | null };​const initialState: UiState = { cartIsVisible: false, notification: null };​const uiSlice = createSlice({  name: "ui",  initialState,  reducers: {    toggleCart(state) {      state.cartIsVisible = !state.cartIsVisible;    },    showNotification(state, action: PayloadAction<Notification>) {      state.notification = action.payload;    },  },});​export const uiActions = uiSlice.actions;export default uiSlice.reducer;
React
// App.tsxconst notification = useAppSelector((state) => state.ui.notification);​return (  <>    {notification && (      <Notification        status={notification.status}        title={notification.title}        message={notification.message}      />    )}    <Layout>{/* ... */}</Layout>  </>);
Angular
// store/ui.actions.tsexport type Notification = {  status: "pending" | "success" | "error";  title: string;  message: string;};​export const UiActions = createActionGroup({  source: "UI",  events: {    "Toggle Cart": emptyProps(),    "Show Notification": props<Notification>(),  },});
Angular
// store/ui.feature.tstype UiState = { cartIsVisible: boolean; notification: Notification | null };​export const uiFeature = createFeature({  name: "ui",  reducer: createReducer(    { cartIsVisible: false, notification: null } as UiState,    on(UiActions.toggleCart, (state) => ({ ...state, cartIsVisible: !state.cartIsVisible })),    on(UiActions.showNotification, (state, { status, title, message }) => ({      ...state,      notification: { status, title, message },    })),  ),});
Angular
// app.component.ts@Component({  selector: "app-root",  imports: [NotificationComponent, LayoutComponent],  template: `    @if (notification(); as n) {      <app-notification [status]="n.status" [title]="n.title" [message]="n.message" />    }    <app-layout />  `,})export class AppComponent {  private store = inject(Store);  protected notification = this.store.selectSignal(uiFeature.selectNotification);}

Quick reference

TaskReactAngular
Define UI stateuiSliceuiFeature
Show notificationdispatch(uiActions.showNotification({...}))store.dispatch(UiActions.showNotification({...}))
Read ituseAppSelector(s => s.ui.notification)selectSignal(uiFeature.selectNotification)