|
| 1 | +--- |
| 2 | +chapter: 7 |
| 3 | +subtitle: Patterns |
| 4 | +--- |
| 5 | + |
| 6 | +An aim of Catalyst is to be as light weight as possible, and so we often avoid including helper functions for otherwise fine code. We also want to keep Catalyst focussed, and so where some helper functions might be reasonable, we recommend judicious use of other small libraries. |
| 7 | + |
| 8 | +Here are a few common patterns which we've avoided introducing into the Catalyst code base, and instead encourage you to take the example code and run with that: |
| 9 | + |
| 10 | + |
| 11 | +### Debouncing or Throttling events |
| 12 | + |
| 13 | +Often times you'll want to do something computationally intensive (or network intensive) based on a user event. It's worth throttling the amount of times a function can be called for these events, to prevent saturation of the CPU or network. For this we can use the "debounce" or "throttle" patterns. We recommend using the [`@github/mini-throttle`](https://github.com/github/mini-throttle) library for this, which provides convenient decorators to put on methods which will add throttling to them: |
| 14 | + |
| 15 | +```typescript |
| 16 | +import {controller} from '@github/catalyst' |
| 17 | +import {debounce} from '@github/mini-throttle/decorators' |
| 18 | + |
| 19 | +@controller |
| 20 | +class FuzzySearchElement extends HTMLElement { |
| 21 | + |
| 22 | + // Adding `@debounce(100)` here means this method will only be called once in a 100ms period. |
| 23 | + @debounce(100) |
| 24 | + search(event: Event) { |
| 25 | + const value = event.currentTarget.value |
| 26 | + // This function is very computationally intensive, so we should run it as little as possible |
| 27 | + this.filterAllItemsWithValue(value) |
| 28 | + } |
| 29 | + |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +### Aborting Network Requests |
| 34 | + |
| 35 | +When making network requests using `fetch`, based on user input, you can cancel old requests as new ones come in, this is useful for performance as well as UI responsiveness, as old requests that aren't cancelled might complete later than newer ones causing the UI to jump around. Aborting network requests requires you to use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) (a web platform feature). |
| 36 | + |
| 37 | +```typescript |
| 38 | +@controller |
| 39 | +class RemoveSearchElement extends HTMLElement { |
| 40 | + |
| 41 | + #remoteSearchController: AbortController|null |
| 42 | + |
| 43 | + async search(event: Event) { |
| 44 | + // Abort the old Request |
| 45 | + this.#remoteSearchController?.abort() |
| 46 | + |
| 47 | + // To start making a new request, construct an AbortController |
| 48 | + const {signal} = (this.#remoteSearchController = new AbortController()) |
| 49 | + |
| 50 | + try { |
| 51 | + const res = await fetch(myUrl, {signal}) |
| 52 | + |
| 53 | + // ... Add logic here with the completed network response |
| 54 | + } catch (e) { |
| 55 | + |
| 56 | + // ... Add logic here if you need to report a failed network request. |
| 57 | + // Do not rethrow for network errors! |
| 58 | + |
| 59 | + } |
| 60 | + |
| 61 | + if (signal.aborted) { |
| 62 | + // Here you can add logic for if the request was cancelled, but |
| 63 | + // usually what you want to do is just return early to avoid |
| 64 | + // cleaning up the loading UI (bear in mind if the request is |
| 65 | + // cancelled then another one will be in its place). |
| 66 | + return |
| 67 | + } |
| 68 | + |
| 69 | + // ... Add cleanup logic here, such as removing `loading` classes. |
| 70 | + |
| 71 | + } |
| 72 | +} |
| 73 | +``` |
0 commit comments