All articles
iOS4 min read

Redux in SwiftUI: When Web Patterns Meet Apple’s Declarative World

Why Redux isn’t dead like VIPER, and how to actually implement it properly

K
Karan Pal
Author
Image generated by AI
Image generated by AI

Last night, at a cafe in Mumbai, I’m debugging this nightmare state bug — you know the type where user data is getting out of sync across three different screens, and nobody can figure out why. My friend (Kalpesh Varma), an Android Engineer, walks over and goes, “Have you tried Redux?”

Redux. In SwiftUI.

I literally stopped typing and stared at him. This is the same pattern everyone uses in React, but I’d never really considered if it could work with Apple’s shiny declarative framework.

The Short Answer (Because You’re Probably in a Hurry)

Yes, Redux absolutely works with SwiftUI. Unlike VIPER — which, let’s face it, is basically architectural roadkill in the SwiftUI world — Redux actually complements SwiftUI’s reactive nature pretty well.

But here’s where it gets interesting…

Why VIPER Died (And Redux Didn’t)

VIPER was built for UIKit’s imperative world. You know, those massive view controllers where you’re manually updating UI elements, handling delegate callbacks, and basically micromanaging every pixel. SwiftUI said “nah” to all that.

Redux, though? It’s about unidirectional data flow and predictable state management. SwiftUI is already reactive and declarative — it’s just missing the state management part for complex apps.

Actually, let me rephrase that… SwiftUI has @State, @ObservedObject, and @StateObject, but once your app grows beyond a few screens, you start feeling the pain.

Real-World Redux + SwiftUI Implementation (The Right Way)

I’ve been working on this fintech app (can’t name it, NDA and all), and we’re using a Redux-like pattern. Here’s how it actually looks:

import SwiftUI
import Combine
import Observation

@Observable
class AppStore {
    var state: AppState
    private var cancellables = Set<AnyCancellable>()
    
    init(initialState: AppState = AppState()) {
        self.state = initialState
    }
    
    func dispatch(_ action: AppAction) {
        let newState = AppReducer.reduce(state: state, action: action)
        
        // This is where the magic happens
        DispatchQueue.main.async {
            self.state = newState
        }
    }
}

@Observable
struct AppState {
    var user: User?
    var transactions: [Transaction] = []
    var isLoading = false
    var errorMessage: String?
}

enum AppAction {
    case login(username: String, password: String)
    case loginSuccess(user: User)
    case loginFailure(error: String)
    case fetchTransactions
    case transactionsLoaded([Transaction])
}

See the difference? Much cleaner, and it actually works better with SwiftUI's observation system.

The SwiftUI Integration (Where Things Get Smooth)

Now, here’s where Redux really shines with SwiftUI. Your views become these beautiful, pure functions of state:

struct TransactionListView: View {
    @Environment(AppStore.self) private var store
    
    var body: some View {
        NavigationView {
            if store.state.isLoading {
                ProgressView("Loading transactions...")
            } else {
                List(store.state.transactions) { transaction in
                    TransactionRow(transaction: transaction)
                }
                .onAppear {
                    store.dispatch(.fetchTransactions)
                }
            }
        }
    }
}

Notice I’m using @Environment now instead of @EnvironmentObject. With @Observable, this is the recommended approach, and honestly, it feels more natural.

Where Redux Gets Tricky in SwiftUI Land

Now, here’s where I have to be real with you — it’s not all sunshine and rainbows. There are some gotchas:

Async Actions Are… Different

In web Redux, you have middleware like Redux-Thunk. SwiftUI? You’re basically rolling your own async handling:

extension AppStore {
    func asyncDispatch(_ asyncAction: @escaping (AppStore) -> Void) {
        asyncAction(self)
    }
}

// Usage
store.asyncDispatch { store in
    Task {
        do {
            let transactions = try await APIClient.fetchTransactions()
            await MainActor.run {
                store.dispatch(.transactionsLoaded(transactions))
            }
        } catch {
            await MainActor.run {
                store.dispatch(.loginFailure(error: error.localizedDescription))
            }
        }
    }
}

Memory Management Can Bite You

Unlike web frameworks, SwiftUI views can be created and destroyed in ways that’ll make your head spin.

When to Actually Use Redux with SwiftUI

Look, I’m not saying every SwiftUI app needs Redux. If you’re building a simple app with a few screens, @State and @Observable objects will do just fine.

But if you’re dealing with:

Then Redux starts making a lot of sense.

The Verdict

Redux isn’t dead in SwiftUI land — it’s actually thriving. Unlike VIPER, which fought against SwiftUI’s philosophy, Redux embraces the reactive, declarative nature that makes SwiftUI so powerful.

The key is understanding that you’re not fighting the framework; you’re enhancing it. SwiftUI handles the view layer beautifully, and Redux handles the state complexity that Apple’s built-in tools just weren’t designed for.

Now, here’s where it gets personal — I used to be a hardcore MVC guy. Then MVVM. Then I tried forcing VIPER into SwiftUI and wanted to throw my MacBook out the window. Redux? It just… works.

Your app becomes more predictable, debugging becomes easier, and your team stops arguing about where to put business logic. Win-win-win.

Coming up next: I’m working on a deep-dive article showing how to build a complete Redux implementation from scratch — no third-party libraries, just pure Swift and SwiftUI. We’ll cover middleware, async actions, testing strategies, and all the production-ready patterns I’ve learned building real apps. Stay tuned!

🎉 Enjoyed this article? Your support means the world to me!

💬 Drop a comment below! I love hearing about your experiences and answering questions

🎬 Subscribe on Youtube and become early subscribers of my channel: https://www.youtube.com/@swift-pal

💼 Let’s connect on LinkedIn for more professional insights: https://www.linkedin.com/in/karan-pal

Happy coding! 🚀

● The newsletter

New articles, straight to your inbox.

No spam, no filler — just new writing on iOS, the web, and AI when it ships. Unsubscribe anytime.

Keep reading