@uppy/react: remove useUppy & reintroduce useUppyState (#5059)

This commit is contained in:
Merlijn Vos 2024-04-17 12:05:48 +02:00 committed by GitHub
parent 6f761fffd5
commit 915a563da9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 282 additions and 84 deletions

View file

@ -10,7 +10,7 @@ const { mkdir, stat, writeFile } = fs.promises
const PACKAGE_JSON_IMPORT = /^\..*\/package.json$/
const SOURCE = 'packages/{*,@uppy/*}/src/**/*.{js,ts}?(x)'
// Files not to build (such as tests)
const IGNORE = /\.test\.[jt]s$|__mocks__|svelte|angular|companion\//
const IGNORE = /\.test\.jsx?$|\.test\.tsx?$|__mocks__|svelte|angular|companion\//;
// Files that should trigger a rebuild of everything on change
const META_FILES = [
'babel.config.js',

View file

@ -61,9 +61,6 @@ The following components are exported from `@uppy/react`:
- `<ProgressBar />` renders [`@uppy/progress-bar`](/docs/progress-bar)
- `<StatusBar />` renders [`@uppy/status-bar`](/docs/status-bar)
{/* prettier-ignore */}
{/* Commented out until the hook is live
### Hooks
`useUppyState(uppy, selector)`
@ -86,12 +83,10 @@ const metaFields = useUppyState(
```
You can see all the values you can access on the
[`State`](https://github.com/transloadit/uppy/blob/main/packages/%40uppy/core/types/index.d.ts#L190)
[`State`](https://github.com/transloadit/uppy/blob/c45407d099d87e25cecaf03c5d9ce59c582ca0dc/packages/%40uppy/core/src/Uppy.ts#L155-L181)
type. If you are accessing plugin state, you would have to look at the types of
the plugin.
\*/}
## Examples
### Example: basic component

View file

@ -10,24 +10,35 @@ import '@uppy/core/dist/style.css'
import '@uppy/dashboard/dist/style.css'
import '@uppy/drag-drop/dist/style.css'
export default function App () {
const RemoteSourcesOptions = {
companionUrl: 'http://companion.uppy.io',
sources: ['GoogleDrive', 'OneDrive', 'Unsplash', 'Zoom', 'Url'],
}
const uppyDashboard = new Uppy({ id: 'dashboard' }).use(RemoteSources, { ...RemoteSourcesOptions })
const uppyModal = new Uppy({ id: 'modal' })
const uppyDragDrop = new Uppy({ id: 'drag-drop' }).use(ThumbnailGenerator)
const uppyDashboard = new Uppy({ id: 'dashboard' }).use(RemoteSources, {
companionUrl: 'http://companion.uppy.io',
sources: ['GoogleDrive', 'OneDrive', 'Unsplash', 'Zoom', 'Url'],
})
const uppyModal = new Uppy({ id: 'modal' })
const uppyDragDrop = new Uppy({ id: 'drag-drop' }).use(ThumbnailGenerator)
export default function App() {
const [open, setOpen] = useState(false)
// TODO: Parcel is having a bad time resolving React inside @uppy/react for some reason.
// We are using Parcel in an odd way and I don't think there is an easy fix.
// const files = useUppyState(uppyDashboard, (state) => state.files)
// drag-drop has no visual output so we test it via the uppy instance
window.uppy = uppyDragDrop
return (
<div style={{ maxWidth: '30em', margin: '5em 0', display: 'grid', gridGap: '2em' }}>
<div
style={{
maxWidth: '30em',
margin: '5em 0',
display: 'grid',
gridGap: '2em',
}}
>
<button type="button" id="open" onClick={() => setOpen(!open)}>
Open Modal
</button>
{/* <p>Dashboard file count: {Object.keys(files).length}</p> */}
<Dashboard id="dashboard" uppy={uppyDashboard} />
<DashboardModal id="modal" open={open} uppy={uppyModal} />

View file

@ -168,7 +168,7 @@
},
"resolutions": {
"@types/eslint@^7.2.13": "^8.2.0",
"@types/react": "^17",
"@types/react": "^18",
"@types/webpack-dev-server": "^4",
"@vitest/utils": "patch:@vitest/utils@npm%3A1.2.1#./.yarn/patches/@vitest-utils-npm-1.2.1-3028846845.patch",
"p-queue": "patch:p-queue@npm%3A7.4.1#./.yarn/patches/p-queue-npm-7.4.1-e0cf0a6f17.patch",

View file

@ -21,11 +21,16 @@
"url": "git+https://github.com/transloadit/uppy.git"
},
"dependencies": {
"@uppy/utils": "workspace:^"
"@uppy/utils": "workspace:^",
"use-sync-external-store": "^1.2.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.2",
"@types/react": "^18.0.8",
"react": "^18.1.0"
"@types/use-sync-external-store": "^0.0.6",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"peerDependencies": {
"@uppy/core": "workspace:^",
@ -34,7 +39,7 @@
"@uppy/file-input": "workspace:^",
"@uppy/progress-bar": "workspace:^",
"@uppy/status-bar": "workspace:^",
"react": "^16.0.0 || ^17.0.0 || ^18.0.0"
"react": "^18.0.0"
},
"peerDependenciesMeta": {
"@uppy/dashboard": {

View file

@ -4,4 +4,4 @@ export { default as DragDrop } from './DragDrop.ts'
export { default as ProgressBar } from './ProgressBar.ts'
export { default as StatusBar } from './StatusBar.ts'
export { default as FileInput } from './FileInput.ts'
export { default as useUppy } from './useUppy.ts'
export { default as useUppyState } from './useUppyState.ts'

View file

@ -1,36 +0,0 @@
import { useEffect, useRef } from 'react'
import { Uppy as UppyCore } from '@uppy/core'
import type { Body, Meta } from '@uppy/utils/lib/UppyFile'
/**
* @deprecated Initialize Uppy outside of the component.
*/
export default function useUppy<M extends Meta, B extends Body>(
factory: () => UppyCore<M, B>,
): UppyCore<M, B> | undefined {
if (typeof factory !== 'function') {
throw new TypeError(
'useUppy: expected a function that returns a new Uppy instance',
)
}
const uppy = useRef<UppyCore<M, B> | undefined>(undefined)
if (uppy.current === undefined) {
uppy.current = factory()
if (!(uppy.current instanceof UppyCore)) {
throw new TypeError(
`useUppy: factory function must return an Uppy instance, got ${typeof uppy.current}`,
)
}
}
useEffect(() => {
return () => {
uppy.current?.close({ reason: 'unmount' })
uppy.current = undefined
}
}, [uppy])
return uppy.current
}

View file

@ -0,0 +1,46 @@
/* eslint-disable react/react-in-jsx-scope */
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react'
import { describe, expect, expectTypeOf, it } from 'vitest'
import { renderHook, render, act } from '@testing-library/react'
import Uppy from '@uppy/core'
import useUppyState from './useUppyState.ts'
describe('useUppyState', () => {
it('should return and update value with the correct type', () => {
const uppy = new Uppy()
const { result, rerender } = renderHook(() =>
useUppyState(uppy, (state) => state.totalProgress),
)
expectTypeOf(result.current).toEqualTypeOf<number>()
expect(result.current).toBe(0)
act(() => uppy.setState({ totalProgress: 50 }))
rerender()
expect(result.current).toBe(50)
rerender()
expect(result.current).toBe(50)
})
it('does not re-render unnecessarily', () => {
const uppy = new Uppy()
let renderCount = 0
const Component = React.memo((props: { uppy: Uppy<any, any> }) => {
const files = useUppyState(props.uppy, (state) =>
Object.values(state.files),
)
renderCount++
return <div>{files.length}</div>
})
const { rerender } = render(<Component uppy={uppy} />)
expect(renderCount).toBe(1)
// Re-render without updating Uppy's state
rerender(<Component uppy={uppy} />)
// It should have been memoized
expect(renderCount).toBe(1)
act(() => uppy.addFile({ name: 'file', data: new Blob() }))
expect(renderCount).toBe(2)
})
})

View file

@ -0,0 +1,23 @@
import type { Uppy, State } from '@uppy/core'
import type { Body, Meta } from '@uppy/utils/lib/UppyFile'
import { useMemo, useCallback } from 'react'
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector'
export default function useUppyState<
M extends Meta = Meta,
B extends Body = Body,
T = any,
>(uppy: Uppy<M, B>, selector: (state: State<M, B>) => T): T {
const subscribe = useMemo(
() => uppy.store.subscribe.bind(uppy.store),
[uppy.store],
)
const getSnapshot = useCallback(() => uppy.store.getState(), [uppy.store])
return useSyncExternalStoreWithSelector(
subscribe,
getSnapshot,
null,
selector,
)
}

View file

@ -3,6 +3,8 @@
"compilerOptions": {
"noImplicitAny": false,
"outDir": "./lib",
"jsxImportSource": "react",
"jsx": "react-jsx",
"paths": {
"@uppy/utils/lib/*": ["../utils/src/*"],
"@uppy/core": ["../core/src/index.js"],
@ -23,7 +25,7 @@
"skipLibCheck": true
},
"include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"],
"exclude": ["./src/**/*.test.tsx"],
"references": [
{
"path": "../utils/tsconfig.build.json"

View file

@ -3,6 +3,8 @@
"compilerOptions": {
"emitDeclarationOnly": false,
"noEmit": true,
"jsxImportSource": "react",
"jsx": "react-jsx",
"paths": {
"@uppy/utils/lib/*": ["../utils/src/*"],
"@uppy/core": ["../core/src/index.js"],

202
yarn.lock
View file

@ -12,6 +12,13 @@ __metadata:
languageName: node
linkType: hard
"@adobe/css-tools@npm:^4.3.2":
version: 4.3.3
resolution: "@adobe/css-tools@npm:4.3.3"
checksum: d21f3786b84911fee59c995a146644a85c98692979097b26484ffa9e442fb1a92ccd68ce984e3e7cf8d5933c3560fbc0ad3e3cd1de50b9a723d1c012e793bbcb
languageName: node
linkType: hard
"@ampproject/remapping@npm:2.3.0":
version: 2.3.0
resolution: "@ampproject/remapping@npm:2.3.0"
@ -3833,6 +3840,15 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime@npm:^7.12.5":
version: 7.24.1
resolution: "@babel/runtime@npm:7.24.1"
dependencies:
regenerator-runtime: ^0.14.0
checksum: 5c8f3b912ba949865f03b3cf8395c60e1f4ebd1033fbd835bdfe81b6cac8a87d85bc3c7aded5fcdf07be044c9ab8c818f467abe0deca50020c72496782639572
languageName: node
linkType: hard
"@babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2":
version: 7.24.4
resolution: "@babel/runtime@npm:7.24.4"
@ -7594,6 +7610,69 @@ __metadata:
languageName: node
linkType: hard
"@testing-library/dom@npm:^9.0.0":
version: 9.3.4
resolution: "@testing-library/dom@npm:9.3.4"
dependencies:
"@babel/code-frame": ^7.10.4
"@babel/runtime": ^7.12.5
"@types/aria-query": ^5.0.1
aria-query: 5.1.3
chalk: ^4.1.0
dom-accessibility-api: ^0.5.9
lz-string: ^1.5.0
pretty-format: ^27.0.2
checksum: dfd6fb0d6c7b4dd716ba3c47309bc9541b4a55772cb61758b4f396b3785efe2dbc75dc63423545c039078c7ffcc5e4b8c67c2db1b6af4799580466036f70026f
languageName: node
linkType: hard
"@testing-library/jest-dom@npm:^6.4.2":
version: 6.4.2
resolution: "@testing-library/jest-dom@npm:6.4.2"
dependencies:
"@adobe/css-tools": ^4.3.2
"@babel/runtime": ^7.9.2
aria-query: ^5.0.0
chalk: ^3.0.0
css.escape: ^1.5.1
dom-accessibility-api: ^0.6.3
lodash: ^4.17.15
redent: ^3.0.0
peerDependencies:
"@jest/globals": ">= 28"
"@types/bun": "*"
"@types/jest": ">= 28"
jest: ">= 28"
vitest: ">= 0.32"
peerDependenciesMeta:
"@jest/globals":
optional: true
"@types/bun":
optional: true
"@types/jest":
optional: true
jest:
optional: true
vitest:
optional: true
checksum: 631aeadbf4e738080ae095242cf1a29a0b4ee2f09c8bdd0d3f00a923707da64c1617e088ba9a961d098481afabdc1d19149fb7ef98edf15132348eb222f345ae
languageName: node
linkType: hard
"@testing-library/react@npm:^14.2.2":
version: 14.2.2
resolution: "@testing-library/react@npm:14.2.2"
dependencies:
"@babel/runtime": ^7.12.5
"@testing-library/dom": ^9.0.0
"@types/react-dom": ^18.0.0
peerDependencies:
react: ^18.0.0
react-dom: ^18.0.0
checksum: cb73df588592d9101429f057eaa6f320fc12524d5eb2acc8a16002c1ee2d9422a49e44841003bba42974c9ae1ced6b134f0d647826eca42ab8f19e4592971b16
languageName: node
linkType: hard
"@tootallnate/once@npm:2":
version: 2.0.0
resolution: "@tootallnate/once@npm:2.0.0"
@ -7646,6 +7725,13 @@ __metadata:
languageName: node
linkType: hard
"@types/aria-query@npm:^5.0.1":
version: 5.0.4
resolution: "@types/aria-query@npm:5.0.4"
checksum: ad8b87e4ad64255db5f0a73bc2b4da9b146c38a3a8ab4d9306154334e0fc67ae64e76bfa298eebd1e71830591fb15987e5de7111bdb36a2221bdc379e3415fb0
languageName: node
linkType: hard
"@types/babel__core@npm:^7.1.14":
version: 7.1.19
resolution: "@types/babel__core@npm:7.1.19"
@ -8189,14 +8275,22 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:^17":
version: 17.0.49
resolution: "@types/react@npm:17.0.49"
"@types/react-dom@npm:^18.0.0":
version: 18.2.23
resolution: "@types/react-dom@npm:18.2.23"
dependencies:
"@types/react": "*"
checksum: ebed13ffc393f96b3dbe2f959c857c3124c20e84718cded13016eaa2e3b650590557ea75584c4d3d3f226b0aae206167c9c81e971632c1f24bbd258f1d295b96
languageName: node
linkType: hard
"@types/react@npm:^18":
version: 18.2.75
resolution: "@types/react@npm:18.2.75"
dependencies:
"@types/prop-types": "*"
"@types/scheduler": "*"
csstype: ^3.0.2
checksum: e2e8001bb16fdf52dc92dbc2f64c8e49b1c6ab5d5da4725c0f27e3d925939e42b835554fecc306ca6b0951801eb784288b63c924bd703834662bbaccf89cb68a
checksum: f77322720744beba6650462c0d75ccc0be27c176bd7b2aff32039ea30e498a78fe6dbce4a1c3fdbf592ccc01286059f332a294a3b6975dbb0d8d5999718ece38
languageName: node
linkType: hard
@ -8260,13 +8354,6 @@ __metadata:
languageName: node
linkType: hard
"@types/scheduler@npm:*":
version: 0.16.2
resolution: "@types/scheduler@npm:0.16.2"
checksum: b6b4dcfeae6deba2e06a70941860fb1435730576d3689225a421280b7742318d1548b3d22c1f66ab68e414f346a9542f29240bc955b6332c5b11e561077583bc
languageName: node
linkType: hard
"@types/semver@npm:^7.3.12":
version: 7.3.13
resolution: "@types/semver@npm:7.3.13"
@ -8375,6 +8462,13 @@ __metadata:
languageName: node
linkType: hard
"@types/use-sync-external-store@npm:^0.0.6":
version: 0.0.6
resolution: "@types/use-sync-external-store@npm:0.0.6"
checksum: a95ce330668501ad9b1c5b7f2b14872ad201e552a0e567787b8f1588b22c7040c7c3d80f142cbb9f92d13c4ea41c46af57a20f2af4edf27f224d352abcfe4049
languageName: node
linkType: hard
"@types/webpack@npm:^5.28.0":
version: 5.28.0
resolution: "@types/webpack@npm:5.28.0"
@ -9604,9 +9698,14 @@ __metadata:
version: 0.0.0-use.local
resolution: "@uppy/react@workspace:packages/@uppy/react"
dependencies:
"@testing-library/jest-dom": ^6.4.2
"@testing-library/react": ^14.2.2
"@types/react": ^18.0.8
"@types/use-sync-external-store": ^0.0.6
"@uppy/utils": "workspace:^"
react: ^18.1.0
react: ^18.2.0
react-dom: ^18.2.0
use-sync-external-store: ^1.2.0
peerDependencies:
"@uppy/core": "workspace:^"
"@uppy/dashboard": "workspace:^"
@ -9614,7 +9713,7 @@ __metadata:
"@uppy/file-input": "workspace:^"
"@uppy/progress-bar": "workspace:^"
"@uppy/status-bar": "workspace:^"
react: ^16.0.0 || ^17.0.0 || ^18.0.0
react: ^18.0.0
peerDependenciesMeta:
"@uppy/dashboard":
optional: true
@ -10893,16 +10992,7 @@ __metadata:
languageName: node
linkType: hard
"aria-query@npm:5.3.0, aria-query@npm:^5.3.0":
version: 5.3.0
resolution: "aria-query@npm:5.3.0"
dependencies:
dequal: ^2.0.3
checksum: 305bd73c76756117b59aba121d08f413c7ff5e80fa1b98e217a3443fcddb9a232ee790e24e432b59ae7625aebcf4c47cb01c2cac872994f0b426f5bdfcd96ba9
languageName: node
linkType: hard
"aria-query@npm:^5.1.3":
"aria-query@npm:5.1.3, aria-query@npm:^5.1.3":
version: 5.1.3
resolution: "aria-query@npm:5.1.3"
dependencies:
@ -10911,6 +11001,15 @@ __metadata:
languageName: node
linkType: hard
"aria-query@npm:5.3.0, aria-query@npm:^5.0.0, aria-query@npm:^5.3.0":
version: 5.3.0
resolution: "aria-query@npm:5.3.0"
dependencies:
dequal: ^2.0.3
checksum: 305bd73c76756117b59aba121d08f413c7ff5e80fa1b98e217a3443fcddb9a232ee790e24e432b59ae7625aebcf4c47cb01c2cac872994f0b426f5bdfcd96ba9
languageName: node
linkType: hard
"arr-diff@npm:^4.0.0":
version: 4.0.0
resolution: "arr-diff@npm:4.0.0"
@ -13387,6 +13486,13 @@ __metadata:
languageName: node
linkType: hard
"css.escape@npm:^1.5.1":
version: 1.5.1
resolution: "css.escape@npm:1.5.1"
checksum: f6d38088d870a961794a2580b2b2af1027731bb43261cfdce14f19238a88664b351cc8978abc20f06cc6bbde725699dec8deb6fe9816b139fc3f2af28719e774
languageName: node
linkType: hard
"cssesc@npm:^3.0.0":
version: 3.0.0
resolution: "cssesc@npm:3.0.0"
@ -14096,6 +14202,20 @@ __metadata:
languageName: node
linkType: hard
"dom-accessibility-api@npm:^0.5.9":
version: 0.5.16
resolution: "dom-accessibility-api@npm:0.5.16"
checksum: 005eb283caef57fc1adec4d5df4dd49189b628f2f575af45decb210e04d634459e3f1ee64f18b41e2dcf200c844bc1d9279d80807e686a30d69a4756151ad248
languageName: node
linkType: hard
"dom-accessibility-api@npm:^0.6.3":
version: 0.6.3
resolution: "dom-accessibility-api@npm:0.6.3"
checksum: c325b5144bb406df23f4affecffc117dbaec9af03daad9ee6b510c5be647b14d28ef0a4ea5ca06d696d8ab40bb777e5fed98b985976fdef9d8790178fa1d573f
languageName: node
linkType: hard
"dom-serialize@npm:^2.2.1":
version: 2.2.1
resolution: "dom-serialize@npm:2.2.1"
@ -21327,6 +21447,15 @@ __metadata:
languageName: node
linkType: hard
"lz-string@npm:^1.5.0":
version: 1.5.0
resolution: "lz-string@npm:1.5.0"
bin:
lz-string: bin/bin.js
checksum: 1ee98b4580246fd90dd54da6e346fb1caefcf05f677c686d9af237a157fdea3fd7c83a4bc58f858cd5b10a34d27afe0fdcbd0505a47e0590726a873dc8b8f65d
languageName: node
linkType: hard
"magic-string@npm:0.30.8, magic-string@npm:^0.30.7":
version: 0.30.8
resolution: "magic-string@npm:0.30.8"
@ -25739,6 +25868,17 @@ __metadata:
languageName: node
linkType: hard
"pretty-format@npm:^27.0.2":
version: 27.5.1
resolution: "pretty-format@npm:27.5.1"
dependencies:
ansi-regex: ^5.0.1
ansi-styles: ^5.0.0
react-is: ^17.0.1
checksum: cf610cffcb793885d16f184a62162f2dd0df31642d9a18edf4ca298e909a8fe80bdbf556d5c9573992c102ce8bf948691da91bf9739bee0ffb6e79c8a8a6e088
languageName: node
linkType: hard
"pretty-format@npm:^29.5.0":
version: 29.5.0
resolution: "pretty-format@npm:29.5.0"
@ -26126,7 +26266,7 @@ __metadata:
languageName: node
linkType: hard
"react-dom@npm:^18.0.0, react-dom@npm:^18.1.0":
"react-dom@npm:^18.0.0, react-dom@npm:^18.1.0, react-dom@npm:^18.2.0":
version: 18.2.0
resolution: "react-dom@npm:18.2.0"
dependencies:
@ -26282,7 +26422,7 @@ __metadata:
languageName: node
linkType: hard
"react@npm:^18.0.0, react@npm:^18.1.0":
"react@npm:^18.0.0, react@npm:^18.1.0, react@npm:^18.2.0":
version: 18.2.0
resolution: "react@npm:18.2.0"
dependencies:
@ -26480,6 +26620,16 @@ __metadata:
languageName: node
linkType: hard
"redent@npm:^3.0.0":
version: 3.0.0
resolution: "redent@npm:3.0.0"
dependencies:
indent-string: ^4.0.0
strip-indent: ^3.0.0
checksum: fa1ef20404a2d399235e83cc80bd55a956642e37dd197b4b612ba7327bf87fa32745aeb4a1634b2bab25467164ab4ed9c15be2c307923dd08b0fe7c52431ae6b
languageName: node
linkType: hard
"redent@npm:^4.0.0":
version: 4.0.0
resolution: "redent@npm:4.0.0"