SwiftUI Hooks: Bringing React‑style Hook Architecture to SwiftUI
SwiftUI Hooks is an open‑source library that adapts React Hooks concepts for SwiftUI, providing state‑and‑lifecycle hooks such as useState, useEffect, useMemo, and custom hooks, along with usage rules, testing utilities, context handling, and installation instructions for iOS development.
Recently, GitHub contributor ra1028 released an open‑source library called SwiftUI‑Hooks (https://github.com/ra1028/SwiftUI-Hooks) that brings the React Hooks mental model to SwiftUI. The library introduces hook‑style state and lifecycle management directly into SwiftUI views without relying on @State or @ObservedObject.
Supported Hook API
The API mirrors React Hooks, making it easy for developers familiar with React. The main hooks are:
useState
Wraps a value in a Binding and triggers view updates when the value changes.
func useState<State>(_ initialState: State) -> Binding<State>
let count = useState(0) // Binding
count.wrappedValue = 123useEffect
Runs a side‑effect function and optionally returns a cleanup closure.
func useEffect(_ computation: HookComputation, _ effect: @escaping () -> (() -> Void)?)
useEffect(.once) {
print("View is mounted")
return {
print("View is unmounted")
}
}useLayoutEffect
Similar to useEffect but runs synchronously during view evaluation.
func useLayoutEffect(_ computation: HookComputation, _ effect: @escaping () -> (() -> Void)?)
useLayoutEffect(.always) {
print("View is being evaluated")
return nil
}useMemo
Caches a computed value until its dependencies change.
func useMemo<Value>(_ computation: HookComputation, _ makeValue: @escaping () -> Value) -> Value
let random = useMemo(.once) {
Int.random(in: 0...100)
}useRef
Provides a mutable reference that does not trigger view updates when mutated.
func useRef<T>(_ initialValue: T) -> RefObject<T>
let value = useRef("text") // RefObject
value.current = "new text"useReducer
Manages state with a reducer function and a dispatch method.
func useReducer<State, Action>(_ reducer: @escaping (State, Action) -> State, initialState: State) -> (state: State, dispatch: (Action) -> Void)
enum Action { case increment, decrement }
func reducer(state: Int, action: Action) -> Int {
switch action {
case .increment: return state + 1
case .decrement: return state - 1
}
}
let (count, dispatch) = useReducer(reducer, initialState: 0)useEnvironment
Accesses values from SwiftUI's EnvironmentValues without using the @Environment wrapper.
func useEnvironment<Value>(_ keyPath: KeyPath
) -> Value
let colorScheme = useEnvironment(\.colorScheme) // ColorSchemeusePublisher & usePublisherSubscribe
Integrates Combine publishers, exposing the latest async status and optionally starting a subscription.
func usePublisher<P: Publisher>(_ computation: HookComputation, _ makePublisher: @escaping () -> P) -> AsyncStatus
let status = usePublisher(.once) { URLSession.shared.dataTaskPublisher(for: url) }
func usePublisherSubscribe<P: Publisher>(_ makePublisher: @escaping () -> P) -> (status: AsyncStatus
, subscribe: () -> Void)
let (status, subscribe) = usePublisherSubscribe { URLSession.shared.dataTaskPublisher(for: url) }useContext
Retrieves the current value from a Context.Provider .
func useContext<T>(_ context: Context<T>.Type) -> T
let value = useContext(Context<Int>.self)Hook Rules
To preserve correct state ordering, hooks must be called only at the top level of a view function and never inside conditionals or loops. They also must be invoked inside a HookScope or a view that conforms to HookView .
Correct usage:
@ViewBuilder
var counterButton: some View {
let count = useState(0) // top‑level hook
Button("You clicked \(count.wrappedValue) times") {
count.wrappedValue += 1
}
}Incorrect usage:
@ViewBuilder
var counterButton: some View {
if condition {
let count = useState(0) // hook inside a condition – wrong
Button("You clicked \(count.wrappedValue) times") {
count.wrappedValue += 1
}
}
}Custom Hooks and Testing
Developers can compose their own hooks to encapsulate reusable state logic. Example: a useTimer hook that provides the current Date at a specified interval.
func useTimer(interval: TimeInterval) -> Date {
let time = useState(Date())
useEffect(.preserved(by: interval)) {
let timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in
time.wrappedValue = $0.fireDate
}
return { timer.invalidate() }
}
return time.wrappedValue
}The hook can be used in a view as follows:
struct Example: HookView {
var hookBody: some View {
let time = useTimer(interval: 1)
Text("Now: \(time)")
}
}Testing custom hooks is possible with withTemporaryHookScope , which creates an isolated hook environment for unit tests.
withTemporaryHookScope { scope in
scope {
let count = useState(0)
count.wrappedValue = 1
}
scope {
let count = useState(0)
XCTAssertEqual(count.wrappedValue, 1) // state persisted across scopes
}
}Context API
SwiftUI already offers EnvironmentValues , but defining custom environment values can be cumbersome. SwiftUI‑Hooks provides a simpler Context wrapper.
typealias ColorSchemeContext = Context
>
struct ContentView: HookView {
var hookBody: some View {
let colorScheme = useState(ColorScheme.light)
ColorSchemeContext.Provider(value: colorScheme) {
darkModeButton
.background(Color(.systemBackground))
.colorScheme(colorScheme.wrappedValue)
}
}
var darkModeButton: some View {
ColorSchemeContext.Consumer { colorScheme in
Button("Use dark mode") { colorScheme.wrappedValue = .dark }
}
}
}Alternatively, useContext can retrieve the provided value directly.
@ViewBuilder
var darkModeButton: some View {
let colorScheme = useContext(ColorSchemeContext.self)
Button("Use dark mode") { colorScheme.wrappedValue = .dark }
}System Requirements & Installation
SwiftUI‑Hooks requires Swift 5.3+, Xcode 12.4+, iOS 13+, macOS 10.15+, tvOS 13+, and watchOS 6+. It can be added via Swift Package Manager, CocoaPods, or Carthage.
SPM: https://github.com/ra1028/SwiftUI-Hooks
CocoaPods: pod 'Hooks', :git => 'https://github.com/ra1028/SwiftUI-Hooks.git'
Carthage: github "ra1028/SwiftUI-Hooks"
Conclusion
SwiftUI‑Hooks brings the powerful, composable state‑management model of React Hooks to the SwiftUI ecosystem, allowing developers to write cleaner, more reusable view logic while following familiar hook rules and patterns.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.