Mobile Development 11 min read

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.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
SwiftUI Hooks: Bringing React‑style Hook Architecture to SwiftUI

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 = 123

useEffect

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) // ColorScheme

usePublisher & 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.

iOSState ManagementSwiftHooksSwiftUIreact-hooks
Sohu Tech Products
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.