Skip to content

Instantly share code, notes, and snippets.

@Phryxia
Created May 13, 2022 05:33
Show Gist options
  • Save Phryxia/a4766dc52b926dfac951ddef43e207f9 to your computer and use it in GitHub Desktop.
Save Phryxia/a4766dc52b926dfac951ddef43e207f9 to your computer and use it in GitHub Desktop.
React component for interactive input for file which can determine to open dialogue or not.
import { DetailedHTMLProps, InputHTMLAttributes, MouseEvent, useRef } from 'react'
interface Props extends DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
isApprovedInitial?: boolean
onBeforeFileSelection(): Promise<boolean>
}
export default function InteractiveFileInput({ isApprovedInitial, onBeforeFileSelection, ...rest }: Props) {
const dom = useRef<HTMLInputElement>(null)
const isApproved = useRef<boolean>(isApprovedInitial ?? false)
async function handleClick(e: MouseEvent<HTMLInputElement>) {
if (isApproved.current) return
e.preventDefault()
isApproved.current = await onBeforeFileSelection()
if (isApproved.current) {
dom.current?.click()
}
}
return <input ref={dom} onClick={handleClick} {...rest} />
}
@Phryxia
Copy link
Author

Phryxia commented May 18, 2022

Example

Sometimes it's hard to decide within a single callback for file selector. You may need to ask user for the agreement, or you may want to peek an api call to decide it. In this case, you can do this with promise chain.

function Test() {
  const [isAgreedOnce, setIsAgreedOnce] = useState<boolean>(false)
  const [isModalShown, setIsModalShown] = useState<boolean>(false)
  const handleAgreeRef = useRef<() => void>(() => {})
  const handleCancelRef = useRef<() => void>(() => {})

  // Some other logics for user agreement....

  // You want to return true if user agreed at least once.
  // If user didn't agreed, modal would be shown.
  function handleBeforeSelection() {
    return new Promise<boolean>((resolve) => {
      if (isAgreedOnce) {
        resolve(true)
        return
      }
      handleAgreeRef.current = () => resolve(true)
      handleCancelRef.current = () => resolve(false)
      setIsModalShown(true)
    })
  }

  function handleAgree() {
    handleAgreeRef.current()
    setIsAgreedOnce(true)
    setIsModalShown(false)
  }

  function handleCancel() {
    handleCancelRef.current()
    setIsModalShown(false)
  }

  return (
    <div>
      <InteractiveFileInput onBeforeFileSelection={handleBeforeSelection} type="file" />
      {isModalShown && <AgreeModal {...{ handleAgree, handleCancel }} />}
    </div>
  )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment