How to Upload files to IPFS with React

November 1, 2022

3 min read

How to Upload files to IPFS with React
Watch on YouTube

Let's make a React component for uploading a file to a distributed storage IPFS.

import { InputWrapperWithErrorMessage } from "lib/ui/inputs/InputWrapper"
import { useEffect } from "react"
import { FileInput } from "lib/ui/inputs/FileInput"
import { SelectedFile } from "lib/ui/inputs/SelectedFile"
import { useUploadFileMutation } from "web3/hooks/useUploadFileMutation"
import { getDistributedFileName } from "web3/utils/getDistributedFileName"

interface Props {
  value?: string
  error?: string
  onChange: (value?: string) => void
}

export const PdfFileInput = ({ value, error, onChange }: Props) => {
  const {
    mutate: uploadFile,
    data: uploadedFileUri,
    reset,
    isLoading,
  } = useUploadFileMutation()

  useEffect(() => {
    if (uploadedFileUri && !value) {
      onChange(uploadedFileUri)
      reset()
    }
  }, [onChange, reset, uploadedFileUri, value])

  return (
    <InputWrapperWithErrorMessage label="Upload a file" error={error}>
      {value ? (
        <SelectedFile
          name={getDistributedFileName(value)}
          onRemove={() => onChange(undefined)}
        />
      ) : (
        <FileInput
          isLoading={isLoading}
          onSubmit={(file) => {
            uploadFile(file)
          }}
          accept={{
            "application/pdf": [".pdf"],
          }}
        />
      )}
    </InputWrapperWithErrorMessage>
  )
}

Here we have a PDFFileInput component with value, error, and onChange properties. First, we render a wrapper that displays the label and error, if any. Then based on value presence, we show either the SelectedFile component or the FileInput.

import styled from "styled-components"
import { Card } from "lib/ui/Card"
import { inputBackgroundCSS, inputBorderRadiusCSS } from "./config"
import { useDropzone, Accept } from "react-dropzone"
import { VStack } from "lib/ui/Stack"
import { Text } from "lib/ui/Text"
import { defaultTransitionCSS } from "lib/ui/animations/transitions"
import { UploadIcon } from "lib/ui/icons/UploadIcon"
import { OutlinedButton } from "lib/ui/buttons/rect/OutlinedButton"

interface Props {
  onSubmit: (file: File) => void
  accept: Accept
  isLoading?: boolean
}

const Container = styled(Card)`
  flex: 1;
  padding: 32px;
  ${inputBorderRadiusCSS};
  ${inputBackgroundCSS};
  cursor: pointer;

  ${defaultTransitionCSS};

  &:hover {
    background: ${({ theme }) => theme.colors.backgroundGlass2.toCssValue()};
  }
`

export const FileInput = ({ onSubmit, accept, isLoading }: Props) => {
  const { getRootProps, getInputProps } = useDropzone({
    maxFiles: 1,
    accept,
    onDrop: (files) => {
      onSubmit(files[0])
    },
  })

  const acceptedExtensions = Object.values(accept).flat()

  return (
    <Container {...getRootProps()}>
      <VStack gap={16} alignItems="center">
        <Text size={34} color="supporting">
          <UploadIcon />
        </Text>
        <Text color="supporting">
          Drag and drop a{" "}
          {acceptedExtensions
            .map((extension) => extension.toUpperCase())
            .join("/")}{" "}
          file here or
        </Text>
        <OutlinedButton isLoading={isLoading} as="div">
          Click to upload
        </OutlinedButton>
      </VStack>
      <input {...getInputProps()} />
    </Container>
  )
}

The FileInput component receives the onSubmit, accept, and isLoading properties. We pass the accept object to the useDropzone hook to allow only specific file types and the onDrop callback that will trigger the onSubmit property. To allow dropping files into area input, we leverage the use dropzone hook from the react-dropzone library and pass the getRootProps to the container and getInputProps to an invisible input element.

Once the user has selected a file, we use the useUploadFileMutation hook. Here we leverage the web3storage library to upload files to IPFS. The put method will return a content identifier from IPFS that we can wrap with the gateway prefix to get an accessible URL. The problem with this hook implementation is exposing the web3storage key with our front-end code. So take this part and use it on the back end. One solution could be to upload the file to S3 using a pre-signed URL and then ask the back-end to duplicate that file to IPFS. Another thing to note is that IPFS might delete your data to make room for other content. To prevent that, you need to use a pinning service. There are plenty of them, but your would need to pay after exceeding the free tier. After upload success, we show the SelectedFile component with the files' name we extract with a tiny getDistributedFileName helper. To reset the field and upload the file again, the user can press the "Remove" button.