
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
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!