Best practice for sharing data between many features?

There are two ways we show how to share state in the case studies:

First, the case study you found shows that you can share state from a parent to a child via an explicit computed property. Maybe the case study doesn't properly explain the technique, but it does apply to your situation. You just need a way to hold both the core feature state and the shared state together, either as a new type or as a tuple:

struct AppState {
  var user: User
  var screenA: ScreenAState
  var screenB: ScreenBState

  var featureAState: (user: User, screen: ScreenA) {
    get { (self.user, self.screenA) }
    set { self.user = newValue.user; self.screenA = newValue.screen }
  }

  var featureBState: ...
}

struct ScreenAState {
  // no User field here
  var something = false
}

And then your reducer would operate on (user: User, screen: ScreenA) instead of just ScreenA, and your reducer would be pulled back along the \.featureAState key path instead of the \.screenA key path. You could also use a typealias or a new struct to wrap that data if that feels better. If you use a struct you could even add @dynamicMemberLookup to make accessing ScreenAs data inside the struct nicer.

You could go even further and just have a fully generic base state struct of everything you want shared:

@dynamicMemberLookup
struct BaseState<State> {
  var user: User
  var state: State

  subscript(...) { ... }
}

And that would allow you to share some state with basically every part of your app.

We actually have a case study that does exactly this, except for dependencies and the Environment. This shows how to create a SystemEnvironment that wraps another environment and will give all reducers access to a base set of dependencies.

Hopefully that is helpful!

6 Likes