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

The new Reducer protocol forcing a reducer to hydrate the state #65

Closed
mortyccp opened this issue Jan 29, 2016 · 5 comments
Closed

The new Reducer protocol forcing a reducer to hydrate the state #65

mortyccp opened this issue Jan 29, 2016 · 5 comments

Comments

@mortyccp
Copy link

With the new Reducer protocol beings

public protocol Reducer : AnyReducer {
    typealias ReducerStateType
    public func handleAction(action: Action, state: Self.ReducerStateType?) -> Self.ReducerStateType
}

It force every reducer observing a particular ReducerStateType to hydrate the state.
If I have a reducer that do not want to hydrate the state or only want to handle the Action if the targeted State exits, I can't achieve it due to this protocol.
I think we should have a better way to handle this.

@Ben-G
Copy link
Member

Ben-G commented Jan 29, 2016

@soapsign, you are bringing up a good point! The current protocol fairly closely resembles the Redux implementation. It has the advantage that you can initialize a Store without the need of initializing the state. In many cases you won't have all information to provide an initial state at that point in time.

The idea of the current protocol is, that a reducer owns a slice of your state and thus should know best how to initialize it.

I however agree that this assumption might not always be correct. Could you maybe share your example? That would make the discussion easier.

I could imagine that it makes sense to offer to initialization paths:

  1. Provide initial state and provide reducer that assumes initialized state
  2. Don't provide initial state and provide reducer that can hydrate

Using a separate protocol we could accomplish this. Definitely worth discussing.

Thanks for bringing this up!

@mortyccp
Copy link
Author

mortyccp commented Feb 4, 2016

Consider my AppState has 2 subState which is StateA and StateB. Then 'AppState' needs to conform to StateType, HasStateA and HasStateB. For HasStateB, the definition is as follow

protocol HasStateB {
    var stateB: StateB? { get set }
}

It is defined as StateB? as this subState should only available for a limited of time for a small part of the application and should be cleaned after use.

Then I have a StateBReducer, which should implement
func handleAction(action: Action, state: HasStateB?) -> HasStateB.
There are a few problem with this requirement.
First, what should I return If I want to hydrate this subState, as HasStateB is a protocol.
Second, the Store call my Reducer no matter what Action is dispatching. This makes the Reducer to hydrate the state as soon as the first Action is dispatched.

The possible solution for 2nd problem is allow a reducer to return Optional, which is something like
`func handleAction(action: Action, state: HasStateB?) -> HasStateB?'.

For the 1st Problem, it seem that the only possible way is the create another Reducer which is StateType alias define to AppState, so to avoid the return type is protocol problem.

Maybe I am doing this optional subState in a wrong way, but this is the problems I face when I want to achieve this optional subState.

@mortyccp
Copy link
Author

mortyccp commented Feb 4, 2016

struct AppState: StateType, HasStateA, HasStateB {
    var stateA: State
    var stateB: State?
}

struct State {
    var value: String
}

protocol HasStateA {
    var stateA: State { get set }
}

protocol HasStateB {
    var stateB: State? { get set }
}

enum StateAAction: Action {
    case ChangeValue(String)
}

enum StateBAction: Action {
    case ChangeValue(String)
}

struct StateAReducer: Reducer {
    func handleAction(action: Action, var state: HasStateA?) -> HasStateA {
        // filtering action
        guard let action = action as? StateAAction else {
            return state!
        }

        switch action {
        case .ChangeValue(let value):
            state?.stateA.value = value
            return state!
        }
    }
}

struct StateBReducer: Reducer {
    func handleAction(action: Action, var state: HasStateB?) -> HasStateB {
        // filtering action
        guard let action = action as? StateBAction else {
            return state!
        }

        // hydrating state if nil
        var stateB = state?.stateB ?? State(value: "B")

        switch action {
        case .ChangeValue(let value):
            stateB.value = value
            state?.stateB = stateB
            return state!
        }
    }
}

I finally manage to implement the optional sub state, turn out that the Self.ReducerStateType? is not intended for substate but for the whole store at all.
However, I think
func handleAction(action: Action, state: Self.ReducerStateType?) -> Self.ReducerStateType
is still ambiguous and misleading, could we make it clear that some Reducers are not take part in hydrating the store?
And one thing I notice is that to hydrate the store with reducer need to take special care to the order of the reducers.

@Ben-G
Copy link
Member

Ben-G commented Feb 4, 2016

@soapsign Thanks a lot for all the details! Yes, a part of the reducer API is a little bit confusing, since it is currently a mix of the original Swift Flow way of handling reducers and the ReduxKit way. I actually prefer many aspects of the ReduxKit approach, because it makes handling substates easier.

I don't have time for a fill write-up now, but I will get back to this 😃

Thanks a lot for the input!

@mortyccp
Copy link
Author

After switching into the solution @Ben-G suggested in #90, the problem solved cleanly.

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

No branches or pull requests

2 participants