8000 Add File Upload Support · Issue #36 · developmentseed/stac-react · GitHub
[go: up one dir, main page]

Skip to content

Add File Upload Support #36

@AliceR

Description

@AliceR

Problem

stac-react currently only supports fetching STAC resources from URLs. There's no support for:

  • Loading STAC JSON from uploaded files
  • Exploring local STAC catalogs
  • Working with STAC files without hosting them
  • Development and testing with local files

Projects like stac-map need to allow users to drag-and-drop or upload STAC JSON files for immediate visualization without requiring the files to be hosted.

Current Behavior

// Only URL-based fetching is supported
const { collection } = useCollection('collection-id'); // Requires API
const { item } = useItem('https://example.com/item.json'); // Requires URL

No way to load from File objects or FileList.

Desired Behavior

import { useStacValue } from '@developmentseed/stac-react';

function StacFileUploader() {
  const [file, setFile] = useState<File | null>(null);
  
  const { value, isLoading, error } = useStacValue({
    file: file,
  });
  
  return (
    <div>
      <input 
        type="file" 
        accept=".json,application/json"
        onChange={(e) => setFile(e.target.files?.[0])}
      />
      {value && <StacViewer value={value} />}
    </div>
  );
}

Use Cases from stac-map

stac-map supports file uploads via:

const fileUpload = useFileUpload({ maxFiles: 1 });

const { value, error } = useStacValue({
  href,
  fileUpload, // Handles uploaded files
});

// In the UI:
<FileUpload.Dropzone>
  <Map value={value} />
</FileUpload.Dropzone>

Proposed Solution

Enhanced Hook Signature

type UseStacValueOptions = {
  // URL-based
  url?: string;
  
  // File-based
  file?: File;
  
  // Raw JSON
  json?: object;
  
  // Fetch options (for URL)
  headers?: Record<string, string>;
  method?: 'GET' | 'POST';
  body?: unknown;
};

function useStacValue(options: UseStacValueOptions): UseStacValueResult;

Implementation

import { useQuery } from '@tanstack/react-query';

function useStacValue(options: UseStacValueOptions): UseStacValueResult {
  const { url, file, json, headers, method, body } = options;
  
  const { data, error, isLoading, isFetching, refetch } = useQuery({
    queryKey: ['stac-value', url, file?.name, json, method],
    queryFn: async () => {
      let value: any;
      
      if (json) {
        // Direct JSON object
        value = json;
      } else if (file) {
        // File upload
        const text = await file.text();
        value = JSON.parse(text);
      } else if (url) {
        // URL fetch
        const response = await fetch(url, {
          method: method || 'GET',
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
            ...headers,
          },
          body: body ? JSON.stringify(body) : undefined,
        });
        
        if (!response.ok) {
          throw new ApiError(
            response.statusText,
            response.status,
            await response.text(),
            response.url
          );
        }
        
        value = await response.json();
      } else {
        throw new Error('Must provide url, file, or json');
      }
      
      return {
        value,
        type: determineStacType(value),
      };
    },
    enabled: !!(url || file || json),
    retry: false,
  });

  return {
    value: data?.value,
    type: data?.type,
    isLoading,
    isFetching,
    error,
    refetch,
  };
}

Example Usage Patterns

1. File Input

function FileUploadExample() {
  const [file, setFile] = useState<File | null>(null);
  const { value, isLoading, error } = useStacValue({ file });
  
  return (
    <div>
      <input 
        type="file" 
        onChange={(e) => setFile(e.target.files?.[0] || null)}
      />
      {isLoading && <p>Loading...</p>}
      {error && <p>Error: {error.message}</p>}
      {value && <pre>{JSON.stringify(value, null, 2)}</pre>}
    </div>
  );
}

2. Drag and Drop

function DragDropExample() {
  const [file, setFile] = useState<File | null>(null);
  const { value } = useStacValue({ file });
  
  const handleDrop = (e: React.DragEvent) => {
    e.preventDefault();
    const droppedFile = e.dataTransfer.files[0];
    if (droppedFile?.type === 'application/json') {
      setFile(droppedFile);
    }
  };
  
  return (
    <div 
      onDrop={handleDrop}
      onDragOver={(e) => e.preventDefault()}
    >
      Drop STAC JSON file here
      {value && <StacViewer value={value} />}
    </div>
  );
}

3. Combined URL and File

function FlexibleLoader({ url, file }) {
  // Automatically uses file if provided, otherwise URL
  const { value, isLoading } = useStacValue({ 
    url: !file ? url : undefined,
    file: file,
  });
  
  return <div>{/* ... */}</div>;
}

File Validation

Add optional validation:

function validateStacFile(file: File): Promise<boolean> {
  return new Promise((resolve) => {
    if (!file.name.endsWith('.json')) {
      resolve(false);
      return;
    }
    
    const reader = new FileReader();
    reader.onload = (e) => {
      try {
        const json = JSON.parse(e.target?.result as string);
        // Check for STAC required fields
        resolve(
          json.type && 
          json.stac_version &&
          json.links
        );
      } catch {
        resolve(false);
      }
    };
    reader.readAsText(file);
  });
}

Benefits

  • ✅ Load STAC from uploaded files
  • ✅ Support drag-and-drop workflows
  • ✅ Enable offline/local STAC exploration
  • ✅ Simplify development and testing
  • ✅ Support in-memory STAC objects
  • ✅ Maintain URL-based fetching

Breaking Changes

None - this extends existing functionality with backward compatibility.

Testing Requirements

  • Test loading from File objects
  • Test loading from URL
  • Test loading from JSON objects
  • Test error handling for invalid files
  • Test file validation
  • Test with different file sizes
  • Test concurrent file and URL loading

Documentation Requirements

  • Document file upload patterns
  • Provide drag-and-drop examples
  • Document file validation
  • Show integration with file input libraries
  • Document security considerations

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0