cbothner/react-activestorage-provider

View on GitHub
src/DirectUploadProvider.js

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * A component that creates an ActiveStorage::Blob in the Rails database,
 * uploads the files directly to the storage service and calls a render function
 * prop to allow the consumer to display the upload’s progress. On completion,
 * it calls back with the signed ids of the created blob objects.
 *
 * @providesModule DirectUploadProvider
 * @flow
 */

import * as React from 'react'

import Upload from './Upload'

import type {
  ActiveStorageFileUpload,
  Origin,
  RenderProps,
  CustomHeaders,
} from './types'

export type DelegatedProps = {|
  directUploadsPath?: string,
  multiple?: boolean,
  onBeforeBlobRequest?: ({
    id: string,
    file: File,
    xhr: XMLHttpRequest,
  }) => mixed,
  onBeforeStorageRequest?: ({
    id: string,
    file: File,
    xhr: XMLHttpRequest,
  }) => mixed,
  render: RenderProps => React.Node,
|}

type Props = {
  ...DelegatedProps,
  origin: Origin,
  onSuccess: (string[]) => mixed,
  headers?: CustomHeaders,
}

type State = {|
  uploading: boolean,
  fileUploads: { [string]: ActiveStorageFileUpload },
|}

class DirectUploadProvider extends React.Component<Props, State> {
  static defaultProps = {
    origin: {},
  }

  state = {
    uploading: false,
    fileUploads: {},
  }

  uploads: Upload[] = []

  render() {
    const { fileUploads } = this.state
    return this.props.render({
      handleChooseFiles: this.handleChooseFiles,
      handleBeginUpload: this.handleBeginUpload,
      handleUpload: this.handleUpload,
      ready: !this.state.uploading,
      uploads: Object.keys(fileUploads).map(key => fileUploads[key]),
    })
  }

  handleUpload = async (files: FileList | File[]) => {
    this.handleChooseFiles(files)
    return this.handleBeginUpload()
  }

  handleChooseFiles = (files: FileList | File[]) => {
    if (this.state.uploading) return

    this.setState({ fileUploads: {} })
    this.uploads = [...files].map(file => this._createUpload(file))
  }

  handleBeginUpload = async () => {
    if (this.state.uploading) return

    this.setState({ uploading: true })

    const signedIds = await Promise.all(
      this.uploads.map(upload => upload.start())
    )

    this.props.onSuccess(signedIds)
    this.uploads = []
    this.setState({ fileUploads: {}, uploading: false })
  }

  handleChangeFileUpload = (fileUpload: {
    [string]: ActiveStorageFileUpload,
  }) =>
    this.setState(({ fileUploads }) => ({
      fileUploads: { ...fileUploads, ...fileUpload },
    }))

  _createUpload(file: File) {
    const {
      directUploadsPath,
      headers,
      onBeforeBlobRequest,
      onBeforeStorageRequest,
      origin,
    } = this.props

    return new Upload(file, {
      directUploadsPath,
      headers,
      onBeforeBlobRequest,
      onBeforeStorageRequest,
      onChangeFile: this.handleChangeFileUpload,
      origin,
    })
  }
}

export default DirectUploadProvider