urql-graphql / urql

The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
https://urql.dev/goto/docs
MIT License
8.54k stars 444 forks source link

RFC: react native file upload support #3535

Open fawzii0x3 opened 3 months ago

fawzii0x3 commented 3 months ago

P.s : I tried to fetch the image blob but it upload the file with 0kb

Summary

Suggesting the addition of file upload support within urql mutations, particularly focusing on compatibility with the Expo platform. By implementing this feature, developers using urql will have the ability to seamlessly handle file uploads in their GraphQL mutations, enhancing the platform's versatility and accommodating diverse application requirements.

Proposed Solution

To address this need, it's recommended to extend urql's mutation capabilities to facilitate file uploads, with special consideration for Expo compatibility. This could involve integrating Expo's file handling APIs or leveraging compatible JavaScript libraries tailored for React Native environments. The implementation should prioritize simplicity and clarity, ensuring that developers can easily incorporate file upload functionality into their urql mutations. Additionally, comprehensive documentation and practical examples will be essential to assist developers in effectively utilizing this feature.

Requirements

kitten commented 3 months ago

This is more of a documentation task to be honest.

For example, to make streamed responses work on React Native some polyfills are needed to replace some broken/missing functionality in fetch on React Native: Specifically to enable this, for instance, you'd need:

In fact, with the latter providing FormData and if Blob is around, this may already be enough to fix it, if react-native-fetch-api accepts FormData bodies.

So, all in all, all we can do is support the web platform and lean either on React Native aligning closer with web standards or lean on polyfills. Hence, it's more a matter of collecting research then creating documentation.

koredefashokun commented 1 month ago

Interestingly, using @urql/exchange-multipart-fetch (although deprecated) allows React Native uploads to work as expected. Is there a specific reason this feature was removed when multipart fetch was added into @urql/core?

I get this error when I try to use the recommended fetchExchange:

[CombinedError: [GraphQL] Variable "$input" got invalid value { name: "upload.jpg", type: "image/jpeg", uri: "file:[truncated]D2B33613.jpg" } at "input.imageFiles[0]"; Upload value invalid.]

Side note: The fetchExchange source seemingly contradicts the documentation here: https://github.com/urql-graphql/urql/blob/main/packages/core/src/exchanges/fetch.ts#L19, which is a bit confusing.

Also, thank you for all the work you do in maintaining this package.

JoviDeCroock commented 1 month ago

Quickly checking out the differences between multipart-fetch and our fetch:

Would you mind checking whether your File has a toJSON() function? Because it's odd that it's printed out in text here, it would make sense if it hit this branch.

kitten commented 1 month ago

If I had to guess, this rather sounds like the import order is an issue here. We do some feature detection in @urql/core, so File may not be polyfilled in early enough in your app

koredefashokun commented 1 month ago

Thank you very much for your assistance. Based on @JoviDeCroock's suggestion, I added a toJSON method to the class I'm using to wrap the file data, which looks like this (it's a modified version of ReactNativeFile from extract-files):

interface ReactNativeFileOptions {
    uri: string;
    name: string;
    type: string;
}

export class ReactNativeFile {
    public uri: string;
    public name: string;
    public type: string;

    constructor({ uri, name, type }: ReactNativeFileOptions) {
        this.uri = uri;
        this.name = name;
        this.type = type;
    }

    toJSON() {
        return { uri: this.uri, name: this.name, type: this.type };
    }
}

The error message looked exactly the same:

[CombinedError: [GraphQL] Variable "$input" got invalid value { name: "upload.jpg", type: "image/jpeg", uri: "file:///Users/korede/Library/Developer/CoreSimulator/Devices/541B3AC8-866E-4131-B54F-E7BA2120C0E4/data/Containers/Data/Application/50BD9A96-DCA0-4FCA-BC1B-D09DB56C26F8/Library/Caches/ExponentExperienceData/@korede360/market-dashboard/ImagePicker/F9AB9A8E-A4C9-44C5-A7F3-9035BF8D620F.jpg" } at "input.imageFiles[0]"; Upload value invalid.]

As for @kitten's suggestion, do you have a recommended way of testing the race condition, or an easy way to hardcode the polyfill within my app, so it loads before the mutation?

Thanks again!

JoviDeCroock commented 1 month ago

That makes sense though, if you wrap the File datatype in a new class then we can't infer that it's a file, because it's neither a File nor Blob - to fix that add extends File to your class.

koredefashokun commented 1 month ago

Thanks! I was finally able to get it to work with this code:

image

I can write a blog post or update the documentation with this method if you want, because it might not be straightforward to many people (at least those using RN)

JoviDeCroock commented 1 month ago

Yeah, we might need to add a block about only File/Blob so custom extensions needing that