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

[v14] Escaped Tracking #521

Merged
merged 23 commits into from
Mar 22, 2025
Merged

[v14] Escaped Tracking #521

merged 23 commits into from
Mar 22, 2025

Conversation

muukii
Copy link
Collaborator

@muukii muukii commented Mar 19, 2025

StoreReader and @reading Documentation

Overview

Verge provides two main ways to read state from a Store in SwiftUI views:

  1. StoreReader - A view that reads state from a Store and displays content according to state changes
  2. @Reading - A property wrapper that provides direct access to Store state

StoreReader

StoreReader is a SwiftUI view that subscribes to state updates from a Store and updates its content when the state changes.

Basic Usage

struct MyView: View {
  let store = Store<State, Never>(initialState: .init())
  
  var body: some View {
    StoreReader(store) { state in
      VStack {
        Text("Count: \(state.count)")
        Button("Increment") {
          store.commit {
            $0.count += 1
          }
        }
      }
    }
  }
}

Key Features

  1. Automatic Updates: The view automatically updates when the tracked state changes
  2. Partial Updates: Only updates when the specific state values used in the view change
  3. Thread Safety: All state updates are handled safely across threads
  4. Computed Properties: Can track changes to computed properties

State Requirements

The state type must use the @Tracking macro:

@Tracking
struct State {
  var count: Int = 0
  
  @Tracking
  struct NestedState {
    var isActive: Bool = false
  }
  
  var nested: NestedState = .init()
}

@reading Property Wrapper

@Reading provides a more direct way to access Store state in SwiftUI views.

Basic Usage

struct MyView: View {
  @Reading<Store<State, Never>> var state: State
  
  init() {
    self._state = .init(
      label: "MyView",
      { Store<State, Never>(initialState: .init()) }
    )
  }
  
  var body: some View {
    VStack {
      Text("Count: \(state.count)")
      Button("Increment") {
        $state.commit {
          $0.count += 1
        }
      }
    }
  }
}

Key Features

  1. Direct State Access: Provides direct access to Store state
  2. Automatic Updates: Updates view when tracked state changes
  3. Two Initialization Modes:
    • Create new Store: init(label: _ driver: () -> Driver)
    • Use existing Store: init(label: mode: _ driver: Driver)
  4. Reference Types: Supports both strong and weak references to Store

Usage with Existing Store

struct MyView: View {
  @Reading<Store<State, Never>> var state: State
  
  init(store: Store<State, Never>) {
    self._state = .init(label: "MyView", store)
  }
  
  var body: some View {
    // Use state directly
  }
}

Best Practices

  1. State Definition:

    • Always use @Tracking macro for state types
    • Use nested @Tracking structs for complex state hierarchies
  2. Store Creation:

    • Create Store instances outside of view body
    • Pass Store instances through initializers when needed
  3. Performance:

    • Both StoreReader and @Reading only update when tracked state changes
    • Use computed properties to derive values from state
  4. Thread Safety:

    • All state mutations should be done through commit method
    • State updates are automatically handled on the main thread

Examples

Complex State with Nested Types

@Tracking
struct State {
  var count: Int = 0
  
  @Tracking
  struct User {
    var name: String = ""
    var age: Int = 0
  }
  
  var currentUser: User = .init()
}

struct UserView: View {
  @Reading<Store<State, Never>> var state: State
  
  var body: some View {
    VStack {
      Text("Name: \(state.currentUser.name)")
      Text("Age: \(state.currentUser.age)")
      Button("Update User") {
        $state.commit {
          $0.currentUser.name = "John"
          $0.currentUser.age = 30
        }
      }
    }
  }
}

Using StoreReader with Computed Properties

@Tracking
struct State {
  var items: [Item] = []
  
  var totalCount: Int {
    items.count
  }
  
  var activeItems: [Item] {
    items.filter { $0.isActive }
  }
}

struct ItemListView: View {
  let store = Store<State, Never>(initialState: .init())
  
  var body: some View {
    StoreReader(store) { state in
      VStack {
        Text("Total Items: \(state.totalCount)")
        Text("Active Items: \(state.activeItems.count)")
      }
    }
  }
}
image

@muukii muukii marked this pull request as ready for review March 22, 2025 14:03
@muukii muukii requested a review from JohnEstropia March 22, 2025 14:06
@muukii muukii changed the title Escaped Tracking [v14] Escaped Tracking Mar 22, 2025
@muukii muukii merged commit 7f719e7 into main Mar 22, 2025
2 checks passed
@muukii muukii deleted the muukii/escaped-tracking branch March 22, 2025 17:37
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

Successfully merging this pull request may close these issues.

1 participant