If you follow me on twitter, you have been spammed these last days with a particular problem I have been tackling.

It all started with this:

And ended with this:

You might have noticed that my initial gut feeling proved to be wrong. 🤷‍♀ī¸

In this post I will add a bit more context to the tweets and structure the thought process I went through.

My initial goal was to:

  1. Add the ability for a ChildViewModel to communicate with a ParentViewModel and change its state.
  2. Use the reducer approach with ReactiveFeedback, because I feel it's the right way to manage state, comparatively to anything else I have seen so far.

This video by Brandon was still in the back of my mind, since I watched it a couple of months ago. The approach with monoids, where the individual reducers are lifted into a global one seemed to be the right one, because it would meet my goal: two distinct entities understanding each other.

After some back and forward it lead me to a couple of problems:

  1. Global store/state. There is now an entity that is aware of all state in the app. I read a couple of posts regarding Redux and the consensus is that this is not problem, but for some reason it feels wrong to me.
  2. Boilerplate. Not only I have to be able to lift: state, actions and effects, I would need to either create a dispatcher for each entity and then lift them to the Store's dispatcher or create a transformation that I can pass to each entity. The reason why I need one or the other, is because I don't want a child to be aware of a parent's actions:

enum ChildAction {
   case createChild
}

enum ParentAction {
   case loadChildren
   case addChildren
   case childrenLoaded([Child])
   case childAdded(Child)   
}

A store's dispatcher will be in the form of:


class Store {
   enum Action {
       case parent(ParentAction)
       case child(ChildAction)
   }
    
   func dispatch(_ action: Action) { }
}

It feels wrong to pass directly a store to a child, or to a parent:

  1. As a parent I shouldn't be able to see my child's action (and vice versa).
  2. A child can potentially change the state of a parent, but it should be indirectly via a change in the child's state.

In retrospective I was naive: I looked at Brandon's approach with all those cool functional concepts: monoids, lens, prisms, composition and my eyes were sparkling. On the other hand I love how ReactiveFeedback solves the problem of generating actions out of effects asynchronously. What did I do? Tried to use both at the same time. The problem is that they work in a different way.

Brandon's approach is what I call an open loop: you create your reducer, you set your initial state and you poke that loop via a dispatcher. When I say "poke" is not in a depreciative way, is just how I see it. ReactiveFeedback is a closed loop: you create your reducer, you set your initial state and finally pass your Feedback loops. After the system has been created, it can still be poked, but not directly.[1]

The only thing missing was to create a relationship between a parent and a child, while using ReactiveFeedback:

struct Relationship<S, A> {
    private let observer: Signal<A, NoError>.Observer
    private let transformation: (S) -> A?

    init(observer: Signal<A, NoError>.Observer, transformation: @escaping (S) -> A?) {
        self.observer = observer
        self.transformation = transformation
    }

    func observe(_ state: S) {
        state |> transformation ?|> observer.send(value:)
    }
}

I initially started with a protocol, like a good citizen. The thing with protocols, is that if you are already working with something abstract, a protocol becomes a nuisance. In this particular case, a Relationship<S, A> is abstract enough for me to test it without hiding it behind a protocol.

The idea with this entity is to receive an S (state) pass it to the observe method, transform it into an A (action) and redirect it to the interested party (in this case the parent). It is then a change in the child state that will generate a new action to the parent.

Assuming the following:

enum ChildState {
   case childCreated(Child)
}

enum ChildAction {
   case createChild
}

enum ParentState {
   case initial
   case loading
   case childrenLoaded([Child])
}

enum ParentAction {
   case loadChildren
   case addChildren
   case childrenLoaded([Child])
   case childAdded(Child)   
}

The flow would go like this:

// In the Parent screen
 -> State (initial) 
 -> Action(loadChildren) 
 -> State (loading) 
 -> Action(childrenLoaded([Child])) 
 -> State (childrenLoaded([Child])) 

// Tap Add Button
 -> Action(addChildren) 
 -> Show Child Screen
      // when the user has added all the necessary information and creates a `Child`
   -> Action(createChild) 
   -> State (childCreated(Child))

At this point the Relationship<S, A> entity is observing the state of the child and the observe function is called with the new state ChildState.childCreated(Child):

    func observe(_ state: S) {
        state |> transformation ?|> observer.send(value:)
    }

This value is then transformed into an action in the parent: ParentAction.childrenAdded(Child). The parent can then append the child to the array of children in the reducer (for simplicity sake I will just show that case):

func reducer(_ state: ParentState, action: ParentAction) -> ParentState {
  switch (state, action) {
  // ....
    case (let .childrenLoaded(children), let .childrenAdded(child)):
       return .childrenLoaded(children + [child])
  // ....
  } 
}

This idea will be pushed here soon, so you can see how everything fits together.

Hopefully I was able to shed some light regarding those tweets and demonstrate how powerful the ReactiveFeedback/Reducer approach is when managing state. ❤ī¸

Any question, let me know!



  1. I am sure someone smarter than me, is able to make this work with both approaches at the same time. ↩ī¸Ž