From 038ad227786aa69cae63aba662036c129d21bb5f Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Fri, 2 Dec 2022 14:47:43 -0800 Subject: [PATCH 1/2] chore: move Uploader into react-uploader package I think it makes more sense to include these "headless UI" style components in the @w3ui/react-* packages rather than bundling them together in the react-ui package. It feels more intuitive given the naming - if we do want to have the "no UI" style components in a separate package I'd advocate for callling that package `react-uploader-provider` or `react-uploader-api` or something, but given how much code this is and the fact that tree shaking is pretty good, it seems ok to me to bundle them together. the react-ui package will be reserved for higher level "customizable UI" style components and can now have opinions about the right way to do customization at the top level rather than having two different flavors of customizability in a single package. --- examples/react/ui/src/ContentPage.js | 4 ++-- packages/react-ui/src/SimpleUploader.tsx | 2 +- packages/react-ui/src/index.ts | 1 - packages/{react-ui => react-uploader}/src/Uploader.tsx | 7 ++++--- packages/react-uploader/src/index.ts | 1 + 5 files changed, 8 insertions(+), 7 deletions(-) rename packages/{react-ui => react-uploader}/src/Uploader.tsx (89%) diff --git a/examples/react/ui/src/ContentPage.js b/examples/react/ui/src/ContentPage.js index d9fc6368..9b0eb279 100644 --- a/examples/react/ui/src/ContentPage.js +++ b/examples/react/ui/src/ContentPage.js @@ -1,6 +1,6 @@ import React from 'react' -import { Uploader, SimpleUploader } from '@w3ui/react-ui' -import { UploaderProvider, useUploader } from '@w3ui/react-uploader' +import { SimpleUploader } from '@w3ui/react-ui' +import { Uploader, UploaderProvider, useUploader } from '@w3ui/react-uploader' import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { a11yDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; diff --git a/packages/react-ui/src/SimpleUploader.tsx b/packages/react-ui/src/SimpleUploader.tsx index 698e543c..d62bd9b4 100644 --- a/packages/react-ui/src/SimpleUploader.tsx +++ b/packages/react-ui/src/SimpleUploader.tsx @@ -1,8 +1,8 @@ import React, { useContext } from 'react' import { CARMetadata } from '@w3ui/uploader-core' +import Uploader, { UploaderContext } from '@w3ui/react-uploader'; import { Link, Version } from 'multiformats' -import Uploader, { UploaderContext } from './Uploader'; export const Uploading = ({ file, storedDAGShards }: { file?: File, storedDAGShards?: CARMetadata[] }) => (
diff --git a/packages/react-ui/src/index.ts b/packages/react-ui/src/index.ts index 4dbe3c95..e900e50e 100644 --- a/packages/react-ui/src/index.ts +++ b/packages/react-ui/src/index.ts @@ -1,2 +1 @@ -export * from './Uploader' export * from './SimpleUploader' diff --git a/packages/react-ui/src/Uploader.tsx b/packages/react-uploader/src/Uploader.tsx similarity index 89% rename from packages/react-ui/src/Uploader.tsx rename to packages/react-uploader/src/Uploader.tsx index 7a312ae6..3406c5d8 100644 --- a/packages/react-ui/src/Uploader.tsx +++ b/packages/react-uploader/src/Uploader.tsx @@ -1,6 +1,7 @@ import React, { useContext, createContext, useState } from 'react' -import { useUploader, CARMetadata } from '@w3ui/react-uploader' import { Link, Version } from 'multiformats' +import { CARMetadata } from '@w3ui/uploader-core' +import { useUploader } from './providers/Uploader' export type UploaderContextValue = { storedDAGShards?: CARMetadata[], @@ -14,7 +15,7 @@ export type UploaderContextValue = { } export const UploaderContext = createContext({ - setFile: () => {} + setFile: () => { } }) export type UploaderProps = { @@ -59,7 +60,7 @@ Uploader.Input = (props: any) => { ) } -Uploader.Form = ({ children, ...props }: {children: React.ReactNode} & any) => { +Uploader.Form = ({ children, ...props }: { children: React.ReactNode } & any) => { const { handleUploadSubmit } = useContext(UploaderContext) return (
diff --git a/packages/react-uploader/src/index.ts b/packages/react-uploader/src/index.ts index 4a5053a5..e6982bbc 100644 --- a/packages/react-uploader/src/index.ts +++ b/packages/react-uploader/src/index.ts @@ -1,2 +1,3 @@ export { uploadFile, uploadDirectory, Service, CARMetadata } from '@w3ui/uploader-core' export * from './providers/Uploader' +export * from './Uploader' From a6f31febeb0df3e1ee1e3768e664d346e51ba8e2 Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Fri, 2 Dec 2022 16:34:06 -0800 Subject: [PATCH 2/2] feat!: rework UploaderComponentContext * make it work more like the UploaderContext in the providers/Uploader.tsx * rename it from UploaderContext to avoid a name collision * be more careful with memoizing the provider value to avoid unecessary re-renders from inline object literal creation (TODO: need to do this in the provider too) --- packages/react-uploader/src/Uploader.tsx | 64 ++++++++++++++++-------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/packages/react-uploader/src/Uploader.tsx b/packages/react-uploader/src/Uploader.tsx index 3406c5d8..bf74b23f 100644 --- a/packages/react-uploader/src/Uploader.tsx +++ b/packages/react-uploader/src/Uploader.tsx @@ -1,31 +1,43 @@ -import React, { useContext, createContext, useState } from 'react' +import React, { useContext, useMemo, createContext, useState } from 'react' import { Link, Version } from 'multiformats' -import { CARMetadata } from '@w3ui/uploader-core' +import { CARMetadata, UploaderContextState, UploaderContextActions } from '@w3ui/uploader-core' import { useUploader } from './providers/Uploader' -export type UploaderContextValue = { - storedDAGShards?: CARMetadata[], - file?: File, - setFile: React.Dispatch>, - dataCid?: Link, +export type UploaderComponentContextState = UploaderContextState & { status?: string, error?: any, + file?: File, handleUploadSubmit?: (e: Event) => void + dataCid?: Link, + storedDAGShards?: CARMetadata[], +} +export type UploaderComponentContextActions = UploaderContextActions & { + setFile: React.Dispatch> } -export const UploaderContext = createContext({ - setFile: () => { } -}) +export type UploaderComponentContextValue = [ + state: UploaderComponentContextState, + actions: UploaderComponentContextActions +] -export type UploaderProps = { - children: React.ReactNode, +const UploaderComponentContext = createContext([ + { storedDAGShards: [] }, + { + setFile: () => { throw new Error('missing set file function') }, + uploadFile: async () => { throw new Error('missing uploader context provider') }, + uploadDirectory: async () => { throw new Error('missing uploader context provider') } + } +]) + +export type HeadlessUploaderProps = { + children?: JSX.Element, } export const Uploader = ({ children, -}: UploaderProps) => { - const [{ storedDAGShards }, uploader] = useUploader() +}: HeadlessUploaderProps) => { + const [uploaderState, uploaderActions] = useUploader() const [file, setFile] = useState() const [dataCid, setDataCid] = useState>() const [status, setStatus] = useState('') @@ -36,32 +48,37 @@ export const Uploader = ({ if (file) { try { setStatus('uploading') - const cid = await uploader.uploadFile(file) + const cid = await uploaderActions.uploadFile(file) setDataCid(cid) } catch (err: any) { - console.error(err) setError(err) } finally { setStatus('done') } } } + + const uploaderComponentContextValue = useMemo(() => [ + { ...uploaderState, file, dataCid, status, error, handleUploadSubmit }, + { ...uploaderActions, setFile } + ], [uploaderState, file, dataCid, status, error, handleUploadSubmit, uploaderActions, setFile]) + return ( - + {children} - + ) } Uploader.Input = (props: any) => { - const { setFile } = useContext(UploaderContext) + const [, { setFile }] = useContext(UploaderComponentContext) return ( setFile(e?.target?.files?.[0])} {...props} /> ) } Uploader.Form = ({ children, ...props }: { children: React.ReactNode } & any) => { - const { handleUploadSubmit } = useContext(UploaderContext) + const [{ handleUploadSubmit }] = useContext(UploaderComponentContext) return ( {children} @@ -69,4 +86,11 @@ Uploader.Form = ({ children, ...props }: { children: React.ReactNode } & any) => ) } +/** + * Use the scoped uploader context state from a parent `Uploader`. + */ +export function useUploaderComponent(): UploaderComponentContextValue { + return useContext(UploaderComponentContext) +} + export default Uploader \ No newline at end of file