> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apexxcloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React SDK

> Documentation for ApexxCloud React SDK

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @apexxcloud/react
  ```

  ```bash yarn theme={null}
  yarn add @apexxcloud/react
  ```
</CodeGroup>

## useApexxCloud Hook

A custom hook for granular control over the upload process.

### Basic Usage

```tsx theme={null}
import { useApexxCloud } from "@apexxcloud/react";
import { useEffect, useRef } from "react";
import { useState } from "react";

export default function CustomUploader() {
  const { upload, uploadMultipart } = useApexxCloud();
  const [progress, setProgress] = useState(0);
  const abortController = useRef(new AbortController());

  const getSignedUrl = async (type: string, params: any) => {
    const response = await fetch(
      `http://localhost:5000/api/files/get-signed-url/${type}`,
      {
        method: "POST",
        body: JSON.stringify(params),
        headers: {
          "Content-Type": "application/json",
        },
      }
    );
    const data = await response.json();
    return data.url;
  };

  const handleUpload = async (file) => {
    setProgress(0);
    abortController.current = new AbortController();

    try {
      await upload(getSignedUrl, file, {
        signal: abortController.current.signal,
        onStart: (event) => {
          console.log(`Starting upload: ${event.file.name}`);
        },
        onProgress: (event) => {
          setProgress(event.progress);
        },
        onComplete: (event) => {
          console.log("Upload complete:", event.response);
          setProgress(0);
        },
        onError: (event) => {
          if (event.type === "abort") {
            console.log("Upload cancelled");
            setProgress(0);
          } else {
            console.error("Upload failed:", event.error);
          }
        },
      });
    } catch (error) {
      if (error instanceof DOMException && error.name === "AbortError") {
        console.log("Upload aborted");
      } else {
        console.error("Upload failed:", error);
      }
      setProgress(0);
    }
  };

  return (
    <div>
      <input
        type="file"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) handleUpload(file);
        }}
      />
      <button
        onClick={() => {
          abortController.current.abort();
          setProgress(0);
        }}
      >
        Cancel Upload
      </button>
      <progress value={progress} max="100" />
    </div>
  );
}
```

### Multipart Upload Example

```tsx theme={null}
import { useApexxCloud } from "@apexxcloud/react";
import { useEffect, useRef } from "react";
import { useState } from "react";

export default function MultipartUploader() {
  const { upload, uploadMultipart } = useApexxCloud();
  const [progress, setProgress] = useState(0);
  const abortController = useRef(new AbortController());

  const getSignedUrl = async (type: string, params: any) => {
    const response = await fetch(
      `http://localhost:5000/api/files/get-signed-url/${type}`,
      {
        method: "POST",
        body: JSON.stringify(params),
        headers: {
          "Content-Type": "application/json",
        },
      }
    );
    const data = await response.json();
    return data.url;
  };

  const handleUpload = async (file) => {
    setProgress(0);
    abortController.current = new AbortController();

    try {
      //  just replace upload with uploadMultipart
      await uploadMultipart(getSignedUrl, file, {
        signal: abortController.current.signal,
        onStart: (event) => {
          console.log(`Starting upload: ${event.file.name}`);
        },
        onProgress: (event) => {
          setProgress(event.progress);
        },
        onComplete: (event) => {
          console.log("Upload complete:", event.response);
          setProgress(0);
        },
        onError: (event) => {
          if (event.type === "abort") {
            console.log("Upload cancelled");
            setProgress(0);
          } else {
            console.error("Upload failed:", event.error);
          }
        },
      });
    } catch (error) {
      if (error instanceof DOMException && error.name === "AbortError") {
        console.log("Upload aborted");
      } else {
        console.error("Upload failed:", error);
      }
      setProgress(0);
    }
  };

  return (
    <div>
      <input
        type="file"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) handleUpload(file);
        }}
      />
      <button
        onClick={() => {
          abortController.current.abort();
          setProgress(0);
        }}
      >
        Cancel Upload
      </button>
      <progress value={progress} max="100" />
    </div>
  );
}
```

### Hook Return Value

| Property              | Type                                                                                           | Description                          |
| --------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------ |
| `upload`              | `(getSignedUrl: GetSignedUrlFn, file: File, options?: UploadOptions) => Promise<any>`          | Function for single file upload      |
| `uploadMultipart`     | `(getSignedUrl: GetSignedUrlFn, file: File, options?: MultipartUploadOptions) => Promise<any>` | Function for multipart upload        |
| `transformImage`      | `TransformBuilder`                                                                             | Builder for image transformations    |
| `transformVideo`      | `VideoTransformBuilder`                                                                        | Builder for video transformations    |
| `transformDocument`   | `DocumentTransformBuilder`                                                                     | Builder for document transformations |
| `transformWithString` | `(path: string, transformations: string \| string[]) => string`                                | Direct transformation using strings  |

### Upload Options

| Option       | Type                             | Description                       |
| ------------ | -------------------------------- | --------------------------------- |
| `signal`     | `AbortSignal`                    | AbortSignal for cancelling upload |
| `onStart`    | `(event: StartEvent) => void`    | Called when upload starts         |
| `onProgress` | `(event: ProgressEvent) => void` | Called during upload progress     |
| `onComplete` | `(event: CompleteEvent) => void` | Called when upload completes      |
| `onError`    | `(event: ErrorEvent) => void`    | Called if upload fails            |

### Multipart Upload Options

Includes all Upload Options plus:

| Option           | Type                                | Description                                    |
| ---------------- | ----------------------------------- | ---------------------------------------------- |
| `partSize`       | `number`                            | Size of each part in bytes (default: 5MB)      |
| `concurrency`    | `number`                            | Number of concurrent part uploads (default: 3) |
| `onPartComplete` | `(part: PartCompleteEvent) => void` | Called when a part completes                   |

### Backend Implementation

See the [JavaScript SDK documentation](/sdks/frontend/javascript#backend-implementation) for details on implementing the backend signed URL endpoint.

## File Uploader Component

The SDK provides a pre-built `FileUploader` component that you can use out of the box:

```tsx theme={null}
import { FileUploader } from "@apexxcloud/react";

function App() {
  const getSignedUrl = async (type, params) => {
    // Your signed URL implementation
  };

  return (
    <FileUploader
      getSignedUrl={getSignedUrl}
      onUploadComplete={(response) => console.log("Upload complete:", response)}
      onUploadError={(error) => console.error("Upload failed:", error)}
      multipart={false}
      accept={{ "image/*": [".png", ".jpg", ".jpeg"] }}
      maxSize={10} // MB
    />
  );
}
```

### Component Props

| Prop               | Type                       | Description                                   |
| ------------------ | -------------------------- | --------------------------------------------- |
| `getSignedUrl`     | `GetSignedUrlFn`           | Function to get signed URLs from your backend |
| `onUploadComplete` | `(response: any) => void`  | Called when upload completes successfully     |
| `onUploadError`    | `(error: Error) => void`   | Called when upload fails                      |
| `multipart`        | `boolean`                  | Enable multipart upload for large files       |
| `accept`           | `Record<string, string[]>` | Accepted file types                           |
| `maxSize`          | `number`                   | Maximum file size in MB                       |

## Custom Implementation

If you want to customize the uploader or build your own, here's the complete implementation of our FileUploader component that you can use as a reference:

<CodeGroup>
  ```tsx FileUploader.tsx theme={null}
  import React, { useCallback, useState, useRef } from "react";
  import { useDropzone } from "react-dropzone";
  import { useApexxCloud } from "../hooks/useApexxCloud";
  import {
    GetSignedUrlFn,
    ProgressEvent,
    MultipartProgressEvent,
  } from "@apexxcloud/sdk-js";
  import clsx from "clsx";
  import "./FileUploader.css";

  interface FileUploaderProps {
    getSignedUrl: GetSignedUrlFn;
    onUploadComplete?: (response: any) => void;
    onUploadError?: (error: Error) => void;
    multipart?: boolean;
    accept?: Record<string, string[]>;
    maxSize?: number;
  }

  export const FileUploader: React.FC<FileUploaderProps> = ({
    getSignedUrl,
    onUploadComplete,
    onUploadError,
    multipart = false,
    accept,
    maxSize,
  }) => {
    const { upload, uploadMultipart } = useApexxCloud();
    const [progress, setProgress] = useState(0);
    const [uploading, setUploading] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const abortControllerRef = useRef<AbortController | null>(null);

    const handleProgress = useCallback(
      (event: ProgressEvent | MultipartProgressEvent) => {
        setProgress(event.progress);
      },
      []
    );

    const handleUpload = useCallback(
      async (file: File) => {
        setError(null);
        setUploading(true);
        setProgress(0);
        abortControllerRef.current = new AbortController();

        try {
          const uploadFn = multipart ? uploadMultipart : upload;
          const result = await uploadFn(getSignedUrl, file, {
            onProgress: handleProgress,
            onComplete: (event) => {
              setProgress(100);
              onUploadComplete?.(event.response);
            },
            onError: (event) => {
              setError(event.error.message);
              onUploadError?.(event.error);
            },
            signal: abortControllerRef.current.signal,
            ...(multipart && {
              partSize: 5 * 1024 * 1024, // 5MB chunks
              concurrency: 3,
            }),
          });
          return result;
        } catch (error) {
          if (error instanceof Error) {
            setError(error.message);
            onUploadError?.(error);
          }
        } finally {
          setUploading(false);
        }
      },
      [
        upload,
        uploadMultipart,
        getSignedUrl,
        multipart,
        onUploadComplete,
        onUploadError,
      ]
    );

    const handleCancel = useCallback(() => {
      abortControllerRef.current?.abort();
      setUploading(false);
      setProgress(0);
    }, []);

    const { getRootProps, getInputProps, isDragActive, fileRejections } =
      useDropzone({
        onDrop: useCallback(
          (acceptedFiles: File[]) => {
            const file = acceptedFiles[0];
            if (file) handleUpload(file);
          },
          [handleUpload]
        ),
        accept,
        maxSize: maxSize ? maxSize * 1024 * 1024 : undefined,
        multiple: false,
        disabled: uploading,
      });

    const acceptedFileTypes = accept
      ? Object.values(accept).flat().join(", ")
      : "All files";
    const maxSizeFormatted = maxSize ? `${maxSize}MB` : "Unlimited";

    return (
      <div className={clsx("apexx-uploader-container")}>
        <div
          {...getRootProps()}
          className={clsx(
            "apexx-uploader-dropzone",
            isDragActive && "active",
            uploading && "disabled"
          )}
        >
          <input {...getInputProps()} />

          {uploading ? (
            <div className="apexx-uploader-progress">
              <div className="apexx-uploader-progress-bar">
                <div
                  className="apexx-uploader-progress-fill"
                  style={{ width: `${progress}%` }}
                />
              </div>
              <div
                style={{
                  display: "flex",
                  justifyContent: "space-between",
                  marginTop: "0.5rem",
                }}
              >
                <span>{progress.toFixed(0)}% complete</span>
                <button
                  onClick={(e) => {
                    e.stopPropagation();
                    handleCancel();
                  }}
                  className="apexx-uploader-cancel"
                >
                  Cancel
                </button>
              </div>
            </div>
          ) : (
            <div className="apexx-uploader-content">
              <svg
                className="apexx-uploader-icon"
                fill="none"
                stroke="currentColor"
                viewBox="0 0 24 24"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={2}
                  d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
                />
              </svg>
              <p>Drop files here or click to upload</p>
              <small className="apexx-uploader-limits">
                Accepted: {acceptedFileTypes}
                <br />
                Max size: {maxSizeFormatted}
              </small>
            </div>
          )}

          {fileRejections.length > 0 && (
            <div className="apexx-uploader-error">
              {fileRejections[0].errors
                .map((err) =>
                  err.code === "file-too-large"
                    ? `File is too large. Maximum size is ${maxSize}MB`
                    : err.message
                )
                .join(", ")}
            </div>
          )}

          {error && <div className="apexx-uploader-error">{error}</div>}
        </div>
      </div>
    );
  };
  ```

  ```css FileUploader.css theme={null}
  .apexx-uploader-container {
    width: 100%;
    max-width: 42rem;
    margin: 0 auto;
  }

  .apexx-uploader-dropzone {
    padding: 1.5rem;
    border: 2px dashed #e5e7eb;
    border-radius: 0.5rem;
    transition: all 0.2s ease;
    background: white;
  }

  .apexx-uploader-dropzone.active {
    border-color: #3b82f6;
    background: #eff6ff;
  }

  .apexx-uploader-dropzone:not(.disabled):hover {
    border-color: #9ca3af;
    cursor: pointer;
  }

  .apexx-uploader-progress {
    margin-top: 1rem;
  }

  .apexx-uploader-progress-bar {
    position: relative;
    width: 100%;
    height: 0.5rem;
    background: #e5e7eb;
    border-radius: 9999px;
    overflow: hidden;
  }

  .apexx-uploader-progress-fill {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    background: #3b82f6;
    transition: width 0.3s ease;
  }

  .apexx-uploader-content {
    text-align: center;
  }

  .apexx-uploader-icon {
    width: 3rem;
    height: 3rem;
    margin: 0 auto 1rem;
    color: #9ca3af;
    transition: color 0.2s ease;
  }

  .active .apexx-uploader-icon {
    color: #3b82f6;
  }

  .apexx-uploader-error {
    margin-top: 1rem;
    padding: 0.75rem;
    background: #fef2f2;
    border-radius: 0.375rem;
    color: #dc2626;
    font-size: 0.875rem;
  }

  .apexx-uploader-cancel {
    padding: 0.25rem 0.75rem;
    color: #dc2626;
    font-size: 0.875rem;
    border-radius: 0.375rem;
    transition: all 0.2s ease;
  }

  .apexx-uploader-cancel:hover {
    background: #fef2f2;
    color: #b91c1c;
  }

  .apexx-uploader-limits {
    color: #666;
    font-size: 0.875rem;
    margin-top: 0.5rem;
  }

  .apexx-uploader-error {
    color: #dc2626;
    margin-top: 0.5rem;
    font-size: 0.875rem;
  }
  ```
</CodeGroup>

This implementation includes:

* Drag and drop support
* Upload progress tracking
* Error handling
* File type and size validation
* Upload cancellation
* Responsive design
* Accessibility features

## Requirements

* React 16.8+ (Hooks support)
* @apexxcloud/sdk-js as a peer dependency

## Transformations

### Image Transformations

```tsx theme={null}
import { useApexxCloud } from "@apexxcloud/react";

function ImageTransformExample() {
  const { transformImage } = useApexxCloud();

  // Basic transformations
  const url = transformImage
    .width(800)
    .height(600)
    .crop("fill")
    .quality("auto:good")
    .buildUrl("path/to/image.jpg");

  // Chaining multiple transformations
  const complexUrl = transformImage
    .width(800)
    .height(600)
    .crop("fill")
    .chain()
    .blur(20)
    .grayscale()
    .buildUrl("path/to/image.jpg");

  return (
    <div>
      <img src={url} alt="Transformed image" />
      <img src={complexUrl} alt="Complex transformed image" />
    </div>
  );
}
```

### Video Transformations

```tsx theme={null}
function VideoTransformExample() {
  const { transformVideo } = useApexxCloud();

  // Basic video transformation
  const videoUrl = transformVideo
    .width(1280)
    .height(720)
    .format("mp4")
    .buildUrl("path/to/video.mp4");

  // Generate video thumbnail
  const thumbnailUrl = transformVideo
    .thumbnail(5.0) // Seek to 5 seconds
    .width(400)
    .height(300)
    .crop("fill")
    .buildUrl("path/to/video.mp4");

  return (
    <div>
      <video src={videoUrl} controls />
      <img src={thumbnailUrl} alt="Video thumbnail" />
    </div>
  );
}
```

### Document Transformations

```tsx theme={null}
function DocumentTransformExample() {
  const { transformDocument } = useApexxCloud();

  // Convert document format
  const pdfUrl = transformDocument
    .format("pdf")
    .quality("auto:high")
    .buildUrl("path/to/document.docx");

  // Generate document thumbnail
  const thumbnailUrl = transformDocument
    .thumbnail(1) // First page
    .width(800)
    .height(1200)
    .buildUrl("path/to/document.pdf");

  return (
    <div>
      <iframe src={pdfUrl} />
      <img src={thumbnailUrl} alt="Document thumbnail" />
    </div>
  );
}
```

For detailed information about available transformation options, see the [JavaScript SDK documentation](/sdks/frontend/javascript#transformations).
