The DropZone component is not usable on Apple iPhones

Has anyone else encountered the same problem? The DropZone component works fine on web and Android phones, but not on iOS devices. Uploading images doesn’t trigger any response or error. Printing the files array in the input event shows it’s empty. Is there some compatibility configuration needed?

Hi @user260,

I tried to reproduce this issue but wasn’t able to.

Which iOS version and browser are you testing with?

This problem occurs specifically on iPhone and iPad devices running iOS 16.1.
This is a screen recording.

We’re encountering the same problem with the new version as well. Are there any other solutions?

Strangely I’m still not able to replicate.
Looking at the video:

  • I see that the DropZone is inside another component. Can you tell me what other components are involved?
  • It looks like the DropZone component itself is working, however the processFiles function might not be working as expected. Can you provide more information about the processFiles function?


The DropZone component is just some structural code. It’s not a problem with processFiles; the files parameter in the onInput event isn’t being passed correctly. I’ve tried several similar apps with the same issue (TrustReviews and TrustWILL).

What is the target that the extension is set to?

I have been able to retrieve image data fine:

Testing code for reference:

import {
  reactExtension,
  CustomerAccountAction,
  Button,
  BlockStack,
  Text,
  DropZone,
} from '@shopify/ui-extensions-react/customer-account';
import React, {useState} from 'react';

// ===========================================
// Extension Targets
// ===========================================

// Button in order action menu
export const menuButton = reactExtension(
  'customer-account.order.action.menu-item.render',
  () => <Button>Test DropZone (iOS)</Button>,
);

// Modal
export default reactExtension(
  'customer-account.order.action.render',
  (api) => <DropZoneModalTest close={api.close} />,
);

// ===========================================
// Configuration
// ===========================================

const MAX_IMAGES = 5;

function DropZoneModalTest({close}) {
  const [uploadedFiles, setUploadedFiles] = useState([]);
  const [debugInfo, setDebugInfo] = useState([]);

  // Adds timestamped message to debug log
  const addDebug = (message) => {
    const timestamp = new Date().toISOString().split('T')[1].slice(0, 12);
    console.log(`[${timestamp}] ${message}`);
    setDebugInfo(prev => [...prev.slice(-9), `[${timestamp}] ${message}`]);
  };

  // DropZone onInput handler - logs file info
  const onInput = async (files) => {
    addDebug(`Files type: ${typeof files}`);
    addDebug(`Files value: ${files}`);
    addDebug(`Files?.length: ${files?.length}`);

    if (!files || !files.length) return;

    const filesArray = Array.from(files);
    const imageFiles = filesArray.filter((file) => file.type.startsWith("image/"));

    // Test file's readability
    for (const file of imageFiles) {
      addDebug(`File: ${file.name}, ${file.type}`);

      try {
        const buffer = await file.arrayBuffer();
        addDebug(`Readable: ${buffer.byteLength} bytes`);
      } catch (e) {
        addDebug(`NOT readable: ${e.message}`);
      }
    }

    if (uploadedFiles.length + imageFiles.length > MAX_IMAGES) return;

    setUploadedFiles(prev => [...prev, ...imageFiles]);
  };

  // ===========================================
  // Render
  // ===========================================

  return (
    <CustomerAccountAction
      title="DropZone iOS Test (Modal)"
      primaryAction={
        <Button onPress={() => close()}>
          Done
        </Button>
      }
      secondaryAction={
        <Button onPress={() => {
          setUploadedFiles([]);
          setDebugInfo([]);
        }}>
          Reset
        </Button>
      }
    >
      <BlockStack spacing="base">
        <Text size="small">Max {MAX_IMAGES} images. Testing DropZone inside modal context.</Text>

        {/* DropZone */}
        <DropZone
          accept="image/*"
          onInput={onInput}
          disabled={uploadedFiles.length >= MAX_IMAGES}
          label={uploadedFiles.length >= MAX_IMAGES ? "Maximum images reached" : "Add Photos"}
          multiple={true}
        />

        {/* Uploaded files list */}
        {uploadedFiles.length > 0 && (
          <BlockStack spacing="tight">
            <Text emphasis="bold">Uploaded ({uploadedFiles.length}/{MAX_IMAGES}):</Text>
            {uploadedFiles.map((file, index) => (
              <Text key={index} size="small">
                ✓ {file.name} ({(file.size / 1024).toFixed(1)} KB) - {file.type}
              </Text>
            ))}
          </BlockStack>
        )}

        {/* Debug log - shows onInput callback data */}
        <BlockStack spacing="tight">
          <Text emphasis="bold" size="small">Debug Log:</Text>
          {debugInfo.length === 0 ? (
            <Text size="small" appearance="subdued">
              Add files to see debug info...
            </Text>
          ) : (
            debugInfo.map((info, index) => (
              <Text key={index} size="small" appearance="subdued">
                {info}
              </Text>
            ))
          )}
        </BlockStack>
      </BlockStack>
    </CustomerAccountAction>
  );
}