Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Modern] Lacking Classic mutation's getFiles() equivalent #1844

Closed
trongthanh opened this issue May 31, 2017 · 9 comments
Closed

[Modern] Lacking Classic mutation's getFiles() equivalent #1844

trongthanh opened this issue May 31, 2017 · 9 comments

Comments

@trongthanh
Copy link
Contributor

The documentation for Mutations of Relay Modern didn't mention how to attach files to upload with the mutation similarly to Classic mutation's getFiles().

Is this feature not available yet in Modern (I'm using react-relay@1.0.0) ? Or the feature lacks documentation?

@apalm
Copy link
Contributor

apalm commented May 31, 2017

I believe you can do something like this:

function fetchQuery(operation, variables, cacheConfig, uploadables) {
  let init;
  // https://github.com/facebook/relay/blob/master/packages/react-relay/classic/network-layer/default/RelayDefaultNetworkLayer.js#L97
  if (uploadables) {
    if (!window.FormData) {
      throw new Error("Uploading files without `FormData` not supported.");
    }
    const formData = new FormData();
    formData.append("query", operation.text);
    formData.append("variables", JSON.stringify(variables));
    for (const uploadable in uploadables) {
      if (Object.prototype.hasOwnProperty.call(uploadables, uploadable)) {
        formData.append(uploadable, uploadables[uploadable]);
      }
    }
    init = {
      method: "POST",
      body: formData
    };
  } else {
    init = {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        query: operation.text,
        variables
      })
    };
  }
  return fetch('/graphql', init).then(response => response.json());
}

@trongthanh
Copy link
Contributor Author

Awesome. Let me try it out and will come back with my findings.

@sibelius
Copy link
Contributor

@trongthanh can we close this?

@trongthanh
Copy link
Contributor Author

trongthanh commented Jul 21, 2017

@sibelius Yes, please.

Here's a complete example of the relay modern environment in case anyone need:

// relay-environment.js
import {
    Environment,
    Network,
    RecordSource,
    Store,
} from 'relay-runtime';

import ErrorHandler from './helpers/ErrorHandler';
import config from './config';
import auth from './auth';

function fetchQuery(operation, variables, cacheConfig, uploadables) {
    const request = {
        method: 'POST',
        headers: {
            Authorization: `JWT ${auth.getToken()}`,
        },
    };

    if (uploadables) {
        if (!window.FormData) {
            throw new Error('Uploading files without `FormData` not supported.');
        }

        const formData = new FormData();
        formData.append('query', operation.text);
        formData.append('variables', JSON.stringify(variables));

        Object.keys(uploadables).forEach(key => {
            if (Object.prototype.hasOwnProperty.call(uploadables, key)) {
                formData.append(key, uploadables[key]);
            }
        });

        request.body = formData;
    } else {
        request.headers['Content-Type'] = 'application/json';
        request.body = JSON.stringify({
            query: operation.text,
            variables,
        });
    }

    return fetch(config.GRAPHQL_ENDPOINT, request)
        .then(response => {
            if (response.status === 200) {
                return response.json();
            }

            // HTTP errors
            // TODO: NOT sure what to do here yet
            return response.json();
        })
        .catch(error => {
            console.log(error);
        });
}

const source = new RecordSource();
const store = new Store(source);

// singleton Environment
export default new Environment({
    // network: networkLayer,
    // handlerProvider,
    network: Network.create(fetchQuery),
    store,
});

Here's a sample mutation with :

import { commitMutation, graphql } from 'react-relay';
import environment from '../../relay-environment';

const mutation = graphql`
    mutation createOnePostMutation ($input: RelayCreateOnePostInput!)  {
        postCreate(input: $input) {
            record {
                id
                title
                content
            }
        }
    }
`;

// import createOnePostMutation from 'createOnePostMutation'
export default ({ post, file, onCompleted, onError }) => {
    const variables = {
        input: {
            record: {
                title: post.title,
                content: post.content,
                authorId: post.authorId,
            }
        },
    };

    let uploadables;

    if (file) {
        uploadables = {
            file,
        };
    }

    commitMutation(environment, {
        mutation,
        variables,
        uploadables,
        onCompleted,
        onError,
    });
};

@nodkz
Copy link
Contributor

nodkz commented Nov 7, 2017

Thanks for example!
Take it for my new OSS package https://github.com/nodkz/react-relay-network-modern

@kaanoner3
Copy link

Is it possible to do chunk upload with relay environment ?

@sibelius
Copy link
Contributor

you can do this inside your network layer

@hyochan
Copy link

hyochan commented Feb 19, 2021

I believe you can do something like this:

function fetchQuery(operation, variables, cacheConfig, uploadables) {
  let init;
  // https://github.com/facebook/relay/blob/master/packages/react-relay/classic/network-layer/default/RelayDefaultNetworkLayer.js#L97
  if (uploadables) {
    if (!window.FormData) {
      throw new Error("Uploading files without `FormData` not supported.");
    }
    const formData = new FormData();
    formData.append("query", operation.text);
    formData.append("variables", JSON.stringify(variables));
    for (const uploadable in uploadables) {
      if (Object.prototype.hasOwnProperty.call(uploadables, uploadable)) {
        formData.append(uploadable, uploadables[uploadable]);
      }
    }
    init = {
      method: "POST",
      body: formData
    };
  } else {
    init = {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        query: operation.text,
        variables
      })
    };
  }
  return fetch('/graphql', init).then(response => response.json());
}

Did anyone tried this solution in React Native? This doesn't seem to work as it did in apollo-upload-client.

@ahmetuysal
Copy link

I believe you can do something like this:

function fetchQuery(operation, variables, cacheConfig, uploadables) {
  let init;
  // https://github.com/facebook/relay/blob/master/packages/react-relay/classic/network-layer/default/RelayDefaultNetworkLayer.js#L97
  if (uploadables) {
    if (!window.FormData) {
      throw new Error("Uploading files without `FormData` not supported.");
    }
    const formData = new FormData();
    formData.append("query", operation.text);
    formData.append("variables", JSON.stringify(variables));
    for (const uploadable in uploadables) {
      if (Object.prototype.hasOwnProperty.call(uploadables, uploadable)) {
        formData.append(uploadable, uploadables[uploadable]);
      }
    }
    init = {
      method: "POST",
      body: formData
    };
  } else {
    init = {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        query: operation.text,
        variables
      })
    };
  }
  return fetch('/graphql', init).then(response => response.json());
}

Did anyone tried this solution in React Native? This doesn't seem to work as it did in apollo-upload-client.

I am using this function on React Native and it seems to be working. Hope it helps. It follows graphql-multipart-request-spec.

const fetchRelay: FetchFunction = async (
  request,
  variables,
  cacheConfig,
  uploadables
) => {
  const requestInit: RequestInit = {
    method: 'POST',
    credentials: 'include',
    headers: {},
  };

  if (uploadables) {
    if (!window.FormData) {
      throw new Error('Uploading files without `FormData` not supported.');
    }

    const formData = new FormData();
    formData.append(
      'operations',
      JSON.stringify({
        query: request.text,
        variables: variables,
      })
    );

    const uploadableMap: {
      [key: string]: string[];
    } = {};

    Object.keys(uploadables).forEach((key) => {
      if (Object.prototype.hasOwnProperty.call(uploadables, key)) {
        uploadableMap[key] = [`variables.${key}`];
      }
    });

    formData.append('map', JSON.stringify(uploadableMap));

    Object.keys(uploadables).forEach((key) => {
      if (Object.prototype.hasOwnProperty.call(uploadables, key)) {
        formData.append(key, uploadables[key]);
      }
    });

    requestInit.body = formData;
  } else {
    requestInit.headers['Content-Type'] = 'application/json';
    requestInit.body = JSON.stringify({
      query: request.text,
      variables,
    });
  }

  const response = await fetch(API_URL, requestInit);

  // Get the response as JSON
  const json = await response.json();

  // GraphQL returns exceptions (for example, a missing required variable) in the "errors"
  // property of the response. If any exceptions occurred when processing the request,
  // throw an error to indicate to the developer what went wrong.
  if (Array.isArray(json.errors)) {
    console.log(json.errors);
    throw new Error(
      `Error fetching GraphQL query '${
        request.name
      }' with variables '${JSON.stringify(variables)}': ${JSON.stringify(
        json.errors
      )}`
    );
  }

  // Otherwise, return the full payload.
  return json;
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants