|
| 1 | +--- |
| 2 | +title: |
| 3 | + 'New Uppy 4.0 major: TypeScript rewrite, Google Photos, React hooks, and much |
| 4 | + more.' |
| 5 | +date: 2024-07-03 |
| 6 | +authors: [aduh95, evgenia, mifi, murderlon] |
| 7 | +image: 'https://uppy.io/img/blog/3.13-3.21/dog-coding-laptop-mars-christmas-tree-2.jpg' |
| 8 | +slug: 'uppy-4.0' |
| 9 | +published: false |
| 10 | +toc_max_heading_level: 2 |
| 11 | +--- |
| 12 | + |
| 13 | +Hi ha ho, this is some goofy introduction. |
| 14 | + |
| 15 | +## TypeScript rewrite |
| 16 | + |
| 17 | +In the year 2024 people expect exellent types from their libaries. We used to |
| 18 | +author types separately by hand but they were often inconsistent or incomplete. |
| 19 | +Now Uppy has been completely rewritten in TypeScript! |
| 20 | + |
| 21 | +From now on you’ll be in safe hands when working with Uppy, whether it’s setting |
| 22 | +the right options, building plugins, or listening to events. |
| 23 | + |
| 24 | +```ts |
| 25 | +import Uppy from '@uppy/core'; |
| 26 | + |
| 27 | +const uppy = new Uppy(); |
| 28 | + |
| 29 | +// Event name autocompletion and inferred argument types |
| 30 | +uppy.on('file-added', (file) => { |
| 31 | + console.log(file); |
| 32 | +}); |
| 33 | +``` |
| 34 | + |
| 35 | +One important thing to note are the new generics on `@uppy/core`. |
| 36 | + |
| 37 | +```ts |
| 38 | +import Uppy from '@uppy/core'; |
| 39 | +// xhr-upload is for uploading to your own backend. |
| 40 | +import XHRUpload from '@uppy/xhr-upload'; |
| 41 | + |
| 42 | +// Your own metadata on files |
| 43 | +type Meta = { myCustomMetadata: string }; |
| 44 | +// The response from your server |
| 45 | +type Body = { someThingMyBackendReturns: string }; |
| 46 | + |
| 47 | +const uppy = new Uppy<Meta, Body>().use(XHRUpload, { |
| 48 | + endpoint: '/upload', |
| 49 | +}); |
| 50 | + |
| 51 | +const id = uppy.addFile({ |
| 52 | + name: 'example.jpg', |
| 53 | + data: new Blob(), |
| 54 | + meta: { myCustomMetadata: 'foo' }, |
| 55 | +}); |
| 56 | + |
| 57 | +// This is now typed |
| 58 | +const { myCustomMetadata } = uppy.getFile(id).meta; |
| 59 | + |
| 60 | +await uppy.upload(); |
| 61 | + |
| 62 | +// This is strictly typed too |
| 63 | +const { someThingMyBackendReturns } = uppy.getFile(id).response; |
| 64 | +``` |
| 65 | + |
| 66 | +Happy inferring! |
| 67 | + |
| 68 | +## Merging the two AWS S3 plugins |
| 69 | + |
| 70 | +We used to two separate plugins for uploading to S3 (and S3-compatible |
| 71 | +services): `@uppy/aws-s3` and `@uppy/aws-s3-multpart`. They have different use |
| 72 | +cases. The advantages of multipart uploads are: |
| 73 | + |
| 74 | +- Improved throughput – You can upload parts in parallel to improve throughput. |
| 75 | +- Quick recovery from any network issues – Smaller part size minimizes the |
| 76 | + impact of restarting a failed upload due to a network error. |
| 77 | +- Pause and resume object uploads – You can upload object parts over time. After |
| 78 | + you initiate a multipart upload, there is no expiry; you must explicitly |
| 79 | + complete or stop the multipart upload. |
| 80 | +- Begin an upload before you know the final object size – You can upload an |
| 81 | + object as you are creating it. |
| 82 | + |
| 83 | +However, the downside is request overhead, as it needs to do creation, signing, |
| 84 | +and completion requests besides the upload requests. For example, if you are |
| 85 | +uploading files that are only a couple kilobytes with a 100ms roundtrip latency, |
| 86 | +you are spending 400ms on overhead and only a few milliseconds on uploading. |
| 87 | +This really adds up if you upload a lot of small files. |
| 88 | + |
| 89 | +AWS, and generally the internet from what we found, tend to agree that **you |
| 90 | +don't want to use multipart uploads for files under 100MB**. But this sometimes |
| 91 | +puts users of our libraries in an awkward position, as their end users may not |
| 92 | +only upload very large files, or only small files. In this case a portion of |
| 93 | +their users get a subpar experience. |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +<!-- TODO: /aws-s3-multipart link should be /aws-s3 (needs site changes) --> |
| 98 | + |
| 99 | +We’ve merged the two plugins into `@uppy/aws-s3` with a new |
| 100 | +[`shouldUseMultipart`](/docs/aws-s3-multipart/#shouldusemultipartfile) option! |
| 101 | +By default it switches to multipart uploads if the file is larger than 100MB. |
| 102 | +You can pass a `boolean` or a function to determine it per file. |
| 103 | + |
| 104 | +## React hooks |
| 105 | + |
| 106 | +People working with React are more likely to create their own user interface on |
| 107 | +top of Uppy than those working with "vanilla" setups. Working with our pre-build |
| 108 | +UI components is a plug-and-play experience, but building on top of Uppy’s state |
| 109 | +with React primitives has been tedious. |
| 110 | + |
| 111 | +To address this we’re introducing to new hooks: `useUppyState` and |
| 112 | +`useUppyEvent`. Thanks to the TypeScript rewrite, we can now do powerful |
| 113 | +inference in hooks as well. |
| 114 | + |
| 115 | +### `useUppyState(uppy, selector)` |
| 116 | + |
| 117 | +Use this hook when you need to access Uppy’s state reactively. |
| 118 | + |
| 119 | +```js |
| 120 | +import Uppy from '@uppy/core'; |
| 121 | +import { useUppyState } from '@uppy/react'; |
| 122 | + |
| 123 | +// IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render. |
| 124 | +const [uppy] = useState(() => new Uppy()); |
| 125 | + |
| 126 | +const files = useUppyState(uppy, (state) => state.files); |
| 127 | +const totalProgress = useUppyState(uppy, (state) => state.totalProgress); |
| 128 | +// We can also get specific plugin state. |
| 129 | +// Note that the value on `plugins` depends on the `id` of the plugin. |
| 130 | +const metaFields = useUppyState( |
| 131 | + uppy, |
| 132 | + (state) => state.plugins?.Dashboard?.metaFields, |
| 133 | +); |
| 134 | +``` |
| 135 | +
|
| 136 | +<!-- TODO: this permalink to State will get outdated. Maybe put it in a file we can link to? --> |
| 137 | +
|
| 138 | +You can see all the values you can access on the |
| 139 | +[`State`](https://github.com/transloadit/uppy/blob/dab8082a4e67c3e7f109eacfbd6c3185f117dc60/packages/%40uppy/core/src/Uppy.ts#L156) |
| 140 | +type. If you are accessing plugin state, you would have to look at the types of |
| 141 | +the plugin. |
| 142 | +
|
| 143 | +### `useUppyEvent(uppy, event, callback)` |
| 144 | +
|
| 145 | +Listen to Uppy [events](/docs/uppy/#events) in a React component. |
| 146 | +
|
| 147 | +Returns an array of which the first item is an array of results from the event. |
| 148 | +Depending on the event, that can be empty or have up to three values. The second |
| 149 | +item is a function to clear the results. |
| 150 | +
|
| 151 | +Values remain in state until the next event (if that ever comes). Depending on |
| 152 | +your use case, you may want to keep the values in state or clear the state after |
| 153 | +something else happened. |
| 154 | +
|
| 155 | +```ts |
| 156 | +import Uppy from '@uppy/core'; |
| 157 | +import { useUppyEvent } from '@uppy/react'; |
| 158 | + |
| 159 | +// IMPORTANT: passing an initializer function |
| 160 | +// to prevent Uppy from being recreated on every render. |
| 161 | +const [uppy] = useState(() => new Uppy()); |
| 162 | + |
| 163 | +const [results, clearResults] = useUppyEvent(uppy, 'transloadit:result'); |
| 164 | +const [stepName, result, assembly] = results; // strongly typed |
| 165 | + |
| 166 | +useUppyEvent(uppy, 'cancel-all', doSomethingElse); |
| 167 | +``` |
| 168 | +
|
| 169 | +## Google Photos |
| 170 | +
|
| 171 | +A long requested feature is finally here: Google Photos support! |
| 172 | +
|
| 173 | +:::info |
| 174 | +
|
| 175 | +Uppy can bring in files from the cloud with [Companion](/docs/companion/). |
| 176 | +
|
| 177 | +Companion is a hosted, standalone, or middleware server to take away the |
| 178 | +complexity of authentication and the cost of downloading files from remote |
| 179 | +sources, such as Instagram, Google Drive, and others. |
| 180 | +
|
| 181 | +This means a 5GB video isn’t eating into your users’ data plans and you don’t |
| 182 | +have to worry about OAuth. |
| 183 | +
|
| 184 | +::: |
| 185 | +
|
| 186 | +<!-- TODO: video of the Google Photos plugin --> |
| 187 | +
|
| 188 | +[`@uppy/google-photos`](/docs/google-photos/) is a new plugin so you can use it |
| 189 | +next to your existing [`@uppy/google-drive`](/docs/google-drive/) plugin. |
| 190 | +
|
| 191 | +## UX improvements for viewing remote files |
| 192 | +
|
| 193 | +When using [Dashboard](/docs/dashboard) with any of our remote sources (such as |
| 194 | +Google Drive) you use our internal `@uppy/provider-views` plugin to navigate and |
| 195 | +select files. |
| 196 | +
|
| 197 | +We now made a handful of quality of life improvements for users. |
| 198 | +
|
| 199 | +| Before | After | |
| 200 | +| :-----------------------------------: | :---: | |
| 201 | +|  | yeah | |
| 202 | +
|
| 203 | +<!-- TODO: video of the improvements--> |
| 204 | +
|
| 205 | +- **Folder caching**. When naviging in and out of folders, you now no longer |
| 206 | + have to wait for the same API call — you’ll see the results instantly. |
| 207 | +- **Indeterminate checkmark states**. Before going into a folder we can’t know |
| 208 | + how many files it contains so checking it immediately will show a traditional |
| 209 | + checkmark. But when you go into a folder and you only select a subset of the |
| 210 | + files, we’ll now show the indeterminate checkmark for the folder when you |
| 211 | + navigate back out. Making it more clear you’re only uploading some of the |
| 212 | + files in that folder. |
| 213 | +- **Reworked restrictions**. Uppy supports file |
| 214 | + [restrictions](/docs/uppy/#restrictions), such as max number of files and max |
| 215 | + file size. It’s now immediately clear in the UI when you are exceeding the max |
| 216 | + number of files you can select. The error notifications are now also more |
| 217 | + clear. |
| 218 | +- **Shift-click multi-select fixes**. You can shift-click the checkboxes to |
| 219 | + select many files at once. This did not always work correctly and it also |
| 220 | + highlighted the files names, which we now improved. |
| 221 | +
|
| 222 | +We’re confident this turns our interface for remote sources into the most |
| 223 | +advanced one out there. We’ve seen some competing libraries not even aggregating |
| 224 | +results beyond the first page API limit of providers. |
| 225 | +
|
| 226 | +## Revamped options for `@uppy/xhr-upload` |
| 227 | +
|
| 228 | +## Simpler configuration for `@uppy/transloadit` |
| 229 | +
|
| 230 | +## Companion |
| 231 | +
|
| 232 | +## And more |
| 233 | +
|
| 234 | +The 4.0 release contained over 170 contributions, many too small to mention, but |
| 235 | +together resulting in Uppy continuing to grow and improve. We closely listen to |
| 236 | +the community and are always looking for ways to improve the experience. |
| 237 | +
|
| 238 | +Since our last blog post other (non-breaking) changes have also been released. |
0 commit comments