Components
Uploader3

Uploader3

Uploader3 is a React-based Web3 image upload component that supports multiple image uploads, image cropping, and uploading images to Web3 Storage providers (like IPFS). There are two ways for uploading, by using a backend API or the Uploader3 Connector.

Features

  • Supports cropping
  • Supports uploading to web3 service providers, such as NFT.storage

Usage

import { Uploader3 } from '@lxdao/uploader3';

Props

PropTypeDescriptionDefault
acceptstringimage accept file type['.png','.jpeg','.jpg','.gif', '.svg']
multiplebooleanmultiple image uploadfalse
apistringendpoint upload api url, post method
headersobjecthttp headers to post api
responseFormatfunctionresponse data format
connectorobjectcreate by uploader3-connector
cropCrop / booleancrop config, set false disabled croptrue
onChangefunctioncallback when files selected
onUploadfunctioncallback when file uploading
onCompletefunctioncallback when file uploaded
onCropEndfunctioncallback when crop end
onCropCancelfunctioncallback when crop cancel

api

api upload serve must return the uploaded file url, and the url must be in the response body.

res.status(200).json({ url: 'https://example.com/xxx.png' });

If you have responseFormat prop, the response body will be passed to responseFormat function.

const responseFormat = (responseBody) => {
  return {
    url: responseBody.data.url,
  };
};

api and connector are mutually exclusive, if both are provided, api will be used. must be provided one of them.

Types reference

Crop

type Crop = {
  size: { width: number; height: number };
  aspectRatio: number;
};

Examples

Basic

Uploader3 children component has the ability to click and drag to select files.

Initializing...
import React from 'react';
import { Uploader3 } from '@lxdao/uploader3';

export default function App() {
  return (
    <div style={{ padding: 10 }}>
      <Uploader3
        api={'/api/upload/file?name=your-name'}
        headers={{
          'x-token': 'abcd',
        }}
        responseFormat={res => {
          console.log('responseFormat', res);
          return res
        }}
        onChange={(files) => {
          console.log('onChange', files);
        }}
        onUpload={(file) => {
          console.log('onUpload', file);
        }}
        onComplete={(result) => {
          console.log('onComplete', result);
        }}
      >
        <div
          style={{
            borderRadius: 5,
            padding: '20px',
            display: 'inline-block',
            boxShadow: '0 0 2px 0 rgba(0, 0, 0, 0.8) inset',
            backgroundColor: '#0987ff',
            color: '#fff',
          }}
        >
          Drop files or Click to select files
        </div>
      </Uploader3>
    </div>
  );
}

Support svg file

Set accept to ['.svg'] only pick svg files, and turn off cropping

Initializing...
import React from 'react';
import { Uploader3 } from '@lxdao/uploader3';
import { Icon } from '@iconify/react';

import { PreviewFile } from './PreviewFile';
import { PreviewWrapper } from './PreviewWrapper';

export default function App() {
  const [file, setFile] = React.useState();

  return (
    <div style={{ padding: 10 }}>
      <Uploader3
        accept={['.svg']}
        api={'/api/upload/file?name=your-name'}
        headers={{
          'x-token': 'abcd',
        }}
        multiple={false}
        crop={false} // must be false when accept is svg
        onChange={(files) => {
          setFile(files[0]);
        }}
        onUpload={(file) => {
          setFile(file);
        }}
        onComplete={(file) => {
          setFile(file);
        }}
        onCropCancel={(file) => {
          setFile(null);
        }}
        onCropEnd={(file) => {
          setFile(file);
        }}
      >
        <PreviewWrapper style={{height: 200, width: 200}}>
          {file ? (
            <PreviewFile file={file} />
          ) : (
            <span>
              <Icon icon={'material-symbols:cloud-upload'} color={'#65a2fa'} fontSize={60} />
            </span>
          )}
        </PreviewWrapper>
      </Uploader3>
     </div>
  );
}

Single file

Set crop to true to enable cropping. use Default crop config, Specify api endpoint url to upload image.

const defaultCropOptions = {
  size: { width: '500px', height: '400px' },
  aspectRatio: 1,
};

If you choose a .svg file and want to keep the file format and upload it directly, you can adjust the cropping ratio to full.

Initializing...
import React from 'react';
import { Uploader3 } from '@lxdao/uploader3';
import { Icon } from '@iconify/react';

import { PreviewFile } from './PreviewFile';
import { PreviewWrapper } from './PreviewWrapper';

export default function App() {
  const [file, setFile] = React.useState();

  return (
    <div style={{ padding: 10 }}>
      <Uploader3
        api={'/api/upload/file?name=your-name'}
        headers={{
          'x-token': 'abcd',
        }}
        multiple={false}
        crop={true} // use default crop options
        onChange={(files) => {
          setFile(files[0]);
        }}
        onUpload={(file) => {
          setFile(file);
        }}
        onComplete={(file) => {
          setFile(file);
        }}
        onCropCancel={(file) => {
          setFile(null);
        }}
        onCropEnd={(file) => {
          setFile(file);
        }}
      >
        <PreviewWrapper style={{height: 200, width: 200}}>
          {file ? (
            <PreviewFile file={file} />
          ) : (
            <span>
              <Icon icon={'material-symbols:cloud-upload'} color={'#65a2fa'} fontSize={60} />
            </span>
          )}
        </PreviewWrapper>
      </Uploader3>
     </div>
  );
}

Single file not crop

Set crop to false to disable cropping.

Initializing...
import React from 'react';
import type { SelectedFile, UploadFile, UploadResult } from '@lxdao/uploader3';
import { Uploader3 } from '@lxdao/uploader3';
import { Icon } from '@iconify/react';

import { PreviewFile } from './PreviewFile';
import { PreviewWrapper } from './PreviewWrapper';

export default function App() {
  const [file, setFile] = React.useState<SelectedFile | UploadFile | UploadResult>();

  return (
    <div style={{ padding: 10 }}>
      <Uploader3
        api={'/api/upload/file'}
        crop={false}
        multiple={false}
        onChange={(files) => {
          setFile(files[0]);
        }}
        onUpload={(file) => {
          setFile(file);
        }}
        onComplete={(file) => {
          setFile(file);
        }}
      >
        <PreviewWrapper>
          {file ? (
            <PreviewFile file={file} />
          ) : (
            <span>
              <Icon icon={'material-symbols:cloud-upload'} color={'#65a2fa'} fontSize={60} />
            </span>
          )}
        </PreviewWrapper>
      </Uploader3>
    </div>
  );
}

Frontend upload with connector

Set connector to Uploader3Connector instance to upload image to web3 storage provider.

⚠️
Use it on a secure network to avoid token disclosure
Initializing...
import React from 'react';
import { Icon } from '@iconify/react';
import { Uploader3 } from '@lxdao/uploader3';
import { createConnector } from '@lxdao/uploader3-connector';

import { PreviewFile } from './PreviewFile';
import { PreviewWrapper } from './PreviewWrapper';

import type { Uploader3Connector } from '@lxdao/uploader3-connector';
import type { CroppedFile, SelectedFile, UploadFile, UploadResult } from '@lxdao/uploader3';

export default function App() {
  const [file, setFile] = React.useState<SelectedFile | UploadFile | UploadResult | CroppedFile | null>();
  const [localToken, setLocalToken] = React.useState<string>('');
  const connector = React.useRef<null | Uploader3Connector.Connector>(null);

  React.useEffect(() => {
    connector.current = createConnector('NFT.storage', {
      token: localToken,
    });
  }, [localToken]);

  return (
    <div style={{ padding: 10 }}>
      <input
        style={{ display: 'block', border: '1px solid #eee', padding: '4px 8px', marginBottom: 10, width: 300 }}
        placeholder='Input your NFT.storage token'
        onInput={(e) => {
          setLocalToken(e.target.value);
        }}
      />
      <Uploader3
        connector={connector.current!}
        multiple={false}
        crop={{
          aspectRatio: 9/16,
          size: { width: 400, height: 300 },
        }}
        onChange={(files) => {
          setFile(files[0]);
        }}
        onUpload={(file) => {
          setFile(file);
        }}
        onComplete={(file) => {
          setFile(file);
        }}
        onCropCancel={(file) => {
          setFile(null);
        }}
        onCropEnd={(file) => {
          setFile(file);
        }}
      >
        <PreviewWrapper>
          {file ? (
            <PreviewFile file={file} />
          ) : (
            <span>
              <Icon icon={'material-symbols:cloud-upload'} color={'#65a2fa'} fontSize={60} />
            </span>
          )}
        </PreviewWrapper>
      </Uploader3>
    </div>
  );
}

Multiple files

Set multiple to true to enable multiple image upload. and enable cropping, when crop end, the cropped image will be one by one uploaded.

Initializing...
import React from 'react';
import { Uploader3 } from '@lxdao/uploader3';
import { Icon } from '@iconify/react';

import { PreviewFile } from './PreviewFile';
import { PreviewWrapper } from './PreviewWrapper';

import type { CroppedFile, SelectedFile, UploadFile, UploadResult } from '@lxdao/uploader3';

export default function App() {
  const [files, setFiles] = React.useState<Array<SelectedFile | UploadFile | UploadResult | CroppedFile>>([]);

  return (
    <div style={{ padding: 10 }}>
      <Uploader3
        api={'/api/upload/file'}
        multiple={true}
        crop={{
          aspectRatio: 4 / 3,
        }}
        onChange={(files) => {
          setFiles(files);
        }}
        onUpload={(file) => {
          setFiles((files) => {
            return files.map((f) => {
              if (f.name === file.name) {
                return file;
              }
              return f;
            });
          });
        }}
        onComplete={(file) => {
          setFiles((files) => {
            return files.map((f) => {
              if (f.name === file.name) {
                return file;
              }
              return f;
            });
          });
        }}
        onCropEnd={(file) => {
          setFiles((files) => {
            return files.map((f) => {
              if (f.name === file.name) {
                return file;
              }
              return f;
            });
          });
        }}
        onCropCancel={(file) => {
          setFiles((files) => {
            return files.filter((f) => f.name !== file.name);
          });
        }}
      >
        <div
          style={{
            borderRadius: 5,
            padding: '20px',
            display: 'flex',
            boxShadow: '0 0 2px 0 rgba(0, 0, 0, 0.8) inset',
            backgroundColor: '#0987ff',
            color: '#fff',
            alignItems: 'center',
          }}
        >
          <Icon icon={'material-symbols:cloud-upload'} color={'#fff'} fontSize={24} />
          <span style={{ paddingLeft: 10 }}>Drop files or Click to select files</span>
        </div>
      </Uploader3>
      <div style={{ display: 'flex', flexWrap: 'wrap', padding: '10px 0' }}>
        {files.map((file) => {
          if (file) {
            return (
              <PreviewWrapper key={file.name + file.status} data-status={file.status}>
                <PreviewFile file={file} />
              </PreviewWrapper>
            );
          } else {
            return null;
          }
        })}
      </div>
    </div>
  );
}

Multiple files not crop

Disable cropping. when select files, the files will be one by one uploaded.

Initializing...
import React from 'react';
import { Icon } from '@iconify/react';
import { Uploader3 } from '@lxdao/uploader3';

import { PreviewFile } from './PreviewFile';
import { PreviewWrapper } from './PreviewWrapper';

import type { CroppedFile, SelectedFile, UploadFile, UploadResult } from '@lxdao/uploader3';

export default function App() {
  const [files, setFiles] = React.useState<Array<SelectedFile | UploadFile | UploadResult | CroppedFile>>([]);

  return (
    <div style={{ padding: 10 }}>
      <Uploader3
        api={'/api/upload/file'}
        multiple={true}
        crop={false}
        onChange={(files) => {
          setFiles(files);
        }}
        onUpload={(file) => {
          setFiles((files) => {
            return files.map((f) => {
              if (f.name === file.name) {
                return file;
              }
              return f;
            });
          });
        }}
        onComplete={(file) => {
          setFiles((files) => {
            return files.map((f) => {
              if (f.name === file.name) {
                return file;
              }
              return f;
            });
          });
        }}
      >
        <div
          style={{
            borderRadius: 5,
            padding: '20px',
            display: 'flex',
            boxShadow: '0 0 2px 0 rgba(0, 0, 0, 0.8) inset',
            backgroundColor: '#0987ff',
            alignItems: 'center',
            color: '#fff',
          }}
        >
          <Icon icon={'material-symbols:cloud-upload'} color={'#fff'} fontSize={24} />
          <span style={{ paddingLeft: 10 }}>Drop files or Click to select files</span>
        </div>
      </Uploader3>
      <div style={{ display: 'flex', flexWrap: 'wrap', padding: '10px 0' }}>
        {files.map((file) => {
          if (file) {
            return (
              <PreviewWrapper key={file.name + file.status} data-status={file.status}>
                <PreviewFile file={file} />
              </PreviewWrapper>
            );
          } else {
            return null;
          }
        })}
      </div>
    </div>
  );
}