@@ -52,7 +52,7 @@ final class TodoListViewModel: Store {
5252 case upsertTodo( Todo )
5353
5454 // Run
55- case triggerSearch ( String )
55+ case applySearchQuery ( String )
5656 case fetchSearchResults( [ TodoListItem ] )
5757 case didToggleCompleted( TodoListItem )
5858 case didTogglePinned( TodoListItem )
@@ -64,6 +64,8 @@ final class TodoListViewModel: Store {
6464 }
6565
6666 enum SideEffect {
67+ case cancelSearch
68+ case debounceSearch( String )
6769 case fetch
6870 case loadNextPage
6971 case search( String )
@@ -134,7 +136,7 @@ final class TodoListViewModel: Store {
134136 case . onAppear, . loadNextPage, . setSearchText, . setToast, . upsertTodo:
135137 effects = reduceByView ( action, state: & state)
136138
137- case . triggerSearch , . fetchSearchResults, . didToggleCompleted, . didTogglePinned,
139+ case . applySearchQuery , . fetchSearchResults, . didToggleCompleted, . didTogglePinned,
138140 . restoreTodo, . setLoading, . appendTodos, . resetPagination, . setHasMore:
139141 effects = reduceByRun ( action, state: & state)
140142 }
@@ -146,6 +148,11 @@ final class TodoListViewModel: Store {
146148 // swiftlint:disable function_body_length
147149 func run( _ effect: SideEffect ) {
148150 switch effect {
151+ case . cancelSearch:
152+ cancelSearch ( )
153+ case . debounceSearch( let keyword) :
154+ beginLoading ( . immediate)
155+ scheduleDebouncedSearch ( keyword)
149156 case . fetch:
150157 beginLoading ( . delayed)
151158 Task {
@@ -312,10 +319,10 @@ private extension TodoListViewModel {
312319 case . setIsSearching( let value) :
313320 state. isSearching = value
314321 if !value {
315- cancelSearch ( )
316322 state. searchText = " "
317323 state. searchResults = [ ]
318324 state. showAllSearchResults = false
325+ return [ . cancelSearch]
319326 }
320327 case . setShowAllSearchResults( let value) :
321328 state. showAllSearchResults = value
@@ -346,12 +353,10 @@ private extension TodoListViewModel {
346353 state. showAllSearchResults = false
347354 let trimmed = text. trimmingCharacters ( in: . whitespacesAndNewlines)
348355 if trimmed. isEmpty {
349- cancelSearch ( )
350356 state. searchResults = [ ]
357+ return [ . cancelSearch]
351358 } else {
352- cancelSearch ( )
353- beginLoading ( . immediate)
354- scheduleDebouncedSearch ( trimmed)
359+ return [ . cancelSearch, . debounceSearch( trimmed) ]
355360 }
356361 case . setToast( let isPresented) :
357362 setToast ( & state, isPresented: isPresented)
@@ -366,8 +371,14 @@ private extension TodoListViewModel {
366371
367372 func reduceByRun( _ action: Action , state: inout State ) -> [ SideEffect ] {
368373 switch action {
369- case . triggerSearch( let query) :
370- return [ . search( query) ]
374+ case . applySearchQuery( let query) :
375+ let trimmed = query. trimmingCharacters ( in: . whitespacesAndNewlines)
376+ if trimmed. isEmpty {
377+ state. searchResults = [ ]
378+ return [ . cancelSearch]
379+ } else {
380+ return [ . search( trimmed) ]
381+ }
371382 case . fetchSearchResults( let items) :
372383 state. searchResults = items
373384 case . didToggleCompleted( let todo) :
@@ -435,7 +446,7 @@ private extension TodoListViewModel {
435446 if Task . isCancelled { return }
436447 await MainActor . run {
437448 self . searchTasks [ . debounce] = nil
438- self . send ( . triggerSearch ( query) )
449+ self . send ( . applySearchQuery ( query) )
439450 }
440451 }
441452 searchTasks [ . debounce] = debounceTask
0 commit comments