Redux are like state containers in SwiftUI. Recommendations

image



Last week we talked about Redux - like state containers in SwiftUI . Redux provides a single source of truth values โ€‹โ€‹that prevents a huge number of potential errors that can occur in different application states. This week weโ€™ll talk about proven methods for creating Redux-based applications that will keep our code base simple and error-free.



Normalization



In the concept of Redux, there is a store object that contains the state of the entire application and this serves as a single source of truth values โ€‹โ€‹for our application. This allows us to synchronize the User Interface with the state of the application. But to achieve this, first, it is necessary to normalize state. Consider the following code example.



struct AppState { var allTasks: [Task] var favorited: [Task] }
      
      





There is an AppState structure that stores a list of simple and selected tasks. It looks simple, but it has one big flaw. Suppose there is a task editing screen where you can change the selected task. Whenever the user clicks the Save button, we need to find and then update a specific task in the list of allTasks (all tasks), and in the list of favorited (selected) tasks. This can lead to errors and performance problems if the list of tasks is too large.



Let's improve the performance of the application a bit by normalizing the state structure. First of all, we must store our tasks in the Dictionary , where the task identifier is the key, and the task itself is the value. A dictionary can obtain a key value for a constant time (O (1)) , but at the same time, it does not preserve order. In this case, you can create an array with identifiers in order to preserve order. Now let's look at the changed state after normalization.



 struct AppState { var tasks: [Int: Task] var allTasks: [Int] var favorited: [Int] }
      
      





As indicated in the above example, tasks are stored in the Dictionary, where the task identifier id is the key, and the task itself is the value. Arrays of identifiers are stored for all tasks and selected tasks. We achieve state stability that synchronizes the user interface and data.



Compositional State



It is very natural to store the state of your application as a single structure, but it can just explode as soon as you add more and more fields to the state structure. We can use compositional state to solve this problem. Let's look at an example.



 struct AppState { var calendar: CalendarState var trends: TrendsState var settings: SettingState }
      
      





In the above code example, we divide our state into three separate parts and combine them into an AppState.



Compositional Reducer



Another important component of a Redux-like state container is Reducer itself. It can be extracted and combined, as was the case with the state structure. This will allow us to comply with the principle of Single Responsibility and maintain objects such as reducers of small sizes.



 enum AppAction { case calendar(action: CalendarAction) case trends(action: TrendsAction) } let trendsReducer: Reducer<TrendsState, TrendsAction> = Reducer { state, action in // Implement your state changes here } let calendarReducer: Reducer<CalendarState, CalendarAction> = Reducer { state, action in // Implement your state changes here } let appReducer: Reducer<AppState, AppAction> = Reducer { state, action in switch action { case let .calendar(action): calendarReducer.reduce(&state.calendar, action) case let .trends(action): trendsReducer.reduce(&state.trends, action) } }
      
      





Conclusion



Today we talked about two important strategies that we should use when developing applications using Redux-like state containers in SwiftUI. Both normalization and integration make the created application simpler and more understandable. I hope you enjoy this article.



Thanks for reading and see you next week!



All Articles