Skip to content

Redux ‐ How to Implement

roux g. buciu edited this page Sep 6, 2024 · 16 revisions

How to Add Redux to a new place

Introduction

So you want to add a Redux action and are unsure of what to do. Well, you've come to the right place! For a more comprehensive discussion of Redux in our application, take a look at this bad boy. This page will only be concerned with the nitty gritty details of how to become the greatest Redux-master the world has every known... for your particular feature.

Process Overview

To add a new Redux component, you'll need to do the following things:

  1. Create an action
  2. Create a state
  3. Create a middleware (this is optional, depending on what you're implementing)
  4. Add the required connections to turn your component into a fully operational battleship

Below, you'll find templates for the work that needs doing.

Actions

The action

final class ExampleAction: Action {
    override init(windowUUID: WindowUUID, actionType: any ActionType) {
        super.init(windowUUID: windowUUID, actionType: actionType)
    }
}

enum ExampleActionType: ActionType {
    case castMagicMissile
}

States

import Redux

struct MainMenuState: ScreenState, Equatable {
    var windowUUID: WindowUUID
    var shouldDismiss: Bool

    init(appState: AppState, uuid: WindowUUID) {
        guard let mainMenuState = store.state.screenState(
            MainMenuState.self,
            for: .mainMenu,
            window: uuid
        ) else {
            self.init(windowUUID: uuid)
            return
        }

        self.init(
            windowUUID: mainMenuState.windowUUID,
            shouldDismiss: mainMenuState.shouldDismiss
        )
    }

    init(windowUUID: WindowUUID) {
        self.init(
            windowUUID: windowUUID,
            shouldDismiss: false
        )
    }

    private init(
        windowUUID: WindowUUID,
        shouldDismiss: Bool
    ) {
        self.windowUUID = windowUUID
        self.shouldDismiss = shouldDismiss
    }

    static let reducer: Reducer<Self> = { state, action in
        guard action.windowUUID == .unavailable || action.windowUUID == state.windowUUID else { return state }

        switch action.actionType {
        case MainMenuMiddlewareActionType.dismissMenu:
            return MainMenuState(
                windowUUID: state.windowUUID,
                shouldDismiss: true
            )
        default:
            return MainMenuState(
                windowUUID: state.windowUUID,
                shouldDismiss: false
            )
        }
    }
}

Connections

The View Controller

import Redux
class ExampleViewController: UIViewController, StoreSubscriber {
    typealias SubscriberStateType = ExampleState

    private var exampleState: ExampleState

    init() {
        subscribeToRedux()
    }

    deinit {
        unsubscribeFromRedux()
    }

    // MARK: - Redux
    func subscribeToRedux() {
        store.dispatch(
            ScreenAction(
                windowUUID: windowUUID,
                actionType: ScreenActionType.showScreen,
                screen: .exampleScreen
            )
        )

        let uuid = windowUUID
        store.subscribe(self, transform: {
            return $0.select({ appState in
                return ExampleState(appState: appState, uuid: uuid)
            })
        })
    }

    func unsubscribeFromRedux() {
        store.dispatch(
            ScreenAction(
                windowUUID: windowUUID,
                actionType: ScreenActionType.closeScreen,
                screen: .exampleScreen
            )
        )
    }

    func newState(state: ExampleState) {
        menuState = state
        // do stuff here
    }
}

Middlewares (Optional)

import Redux
import ToolbarKit

final class MainMenuMiddleware {
    private let logger: Logger
    private let telemetry = MainMenuTelemetry()

    init(logger: Logger = DefaultLogger.shared) {
        self.logger = logger
    }

    lazy var mainMenuProvider: Middleware<AppState> = { state, action in
        switch action.actionType {
        case MainMenuActionType.mainMenuDidAppear:
            self.telemetry.mainMenuViewed()
        case MainMenuActionType.closeMenu:
            self.dismissMenu(windowUUID: action.windowUUID)
        default:
            break
        }
    }

    // MARK: - Action Helpers
    private func dismissMenu(windowUUID: WindowUUID) {
        let action = MainMenuAction(
            windowUUID: windowUUID,
            actionType: MainMenuMiddlewareActionType.dismissMenu
        )
        store.dispatch(action)
    }
}
Clone this wiki locally