Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logging lift operations ? #1

Open
npvisual opened this issue Aug 27, 2020 · 4 comments
Open

Logging lift operations ? #1

npvisual opened this issue Aug 27, 2020 · 4 comments

Comments

@npvisual
Copy link

@luizmb, I am finally getting to look at the log output from the LoggerMiddleware. It's pretty awesome so far !

Issue

I was debugging a state change for a modal sheet that was supposed to be presented when a button / icon was pressed.. but was not 🤨.

...
    var body: some View {
        NavigationView {
            List {
                ...
            }
            .navigationBarItems(trailing: profileButton)
            .sheet(isPresented: viewModel.binding[\.isShowingUserProfile]) { Text("User Profile") }
        }
    }

    var profileButton: some View {
        Button(action: { viewModel.dispatch(.showUserProfile) }) {
            Image(systemName: viewModel.state.iconName)
                .imageScale(.large)
                .accessibility(
                    label: Text(viewModel.state.iconAccessibilityLabel.localizedCapitalized)
                )
                .padding()
        }
    }

Initially I was seeing the appropriate actions being triggered :

2020-08-27 15:07:11.374696-0500 App[7300:453348]
🕹 navigation(App.NavigationAction.showUserProfile)
🎪 Home.swift:34 profileButton

However AppState was never changing. Took me 5 min (!) to figure out that the Reducer for that Navigation part had never been added to the global Reducer.app to lift the NavigationAction :

extension Reducer where ActionType == AppAction, StateType == AppState {
    static let app =
<reducer1>
          <>
<reducer2>
          <>
<reducer3>
---- missing --->
          <> Reducer<NavigationAction, AppState>.navigation.lift(
            action: \AppAction.navigation
---- missing ---<
        ) <> Reducer<AppLifecycleAction, AppLifecycle>.lifecycle.lift(
            action: \AppAction.appLifecycle,
            state: \AppState.appLifecycle
        )
}

Once this was added, things worked great. However the trace output was no different than what I had before (except for the State that had not changed).

Request

Would it be possible to have a trace that highlights the "part" reducers getting lifted all the way up to the top Reducer (i.e. seeing the chain) so that, in my case for example, I would have seen that while the

🕹 navigation(App.NavigationAction.showUserProfile)

was indeed being triggered, it was going nowhere.

@luizmb
Copy link
Member

luizmb commented Aug 27, 2020

We could wrap every reducer in Logger Reducer which would be something like this:

let loggerReducer: (String, Reducer<AppAction, AppState>) -> Reducer<AppAction, AppState> = { name, reducer in
    return Reducer { action, state in
        print("Reducing \(action) on \(name)")
        reducer(action, &state)
    }
}

// Usage
loggerReducer("SomeReducer", someReducer)
<> loggerReducer("NavigationReducer", Reducer<NavigationAction, AppState>.navigation.lift(action: \AppAction.navigation))

Written without compiler, it may need some adjustments.
My only problem with that is the fact that reducer is performing side-effect (print), which is not a big deal.
If you plan to print more that action, for example state diff, then I recommend dispatching to a low-QoS queue to avoid overloading your main queue.

@luizmb
Copy link
Member

luizmb commented Aug 27, 2020

If you make this a func generic over ActionType and StateType, then you can wrap it before lifting, which will be great because will discard any action that the inner reducer doesn't actually handle.

@npvisual
Copy link
Author

I agree that having side-effects on the Reducer isn't ideal. The wrapper would be a good start. I was also looking at potentially adding logging inside the Reducer's reduce func. Not sure yet which way I'll go.

@luizmb
Copy link
Member

luizmb commented Aug 28, 2020

The wrapper can easily become an extension such as:

Reducer<NavigationAction, AppState>
  .navigation
  .logging("Navigation")
  .lift(action: \AppAction.navigation)

Adding to the "reduce" function makes it baked into your app logic and hard to reuse, imo.
".logging" extension may also have a #if DEBUG condition so you don't log that in production.

extension Reducer {
    func logging(_ name: String) -> Reducer {
        Reducer { action, state in
            print("Reducing \(action) on \(name)")
            self.reduce(action, &state)
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants