Apollo boost - __typename in query prevent new mutation
Asked Answered
A

3

2

I have a problem in my meteor/react/apollo (with boost) project. When I query data from the server, it adds __typename to every object and subobject in my query but it my case it creates a major issue as I normally reuse these data to send them to other mutation. Now the other mutation tell me there is an error because the __typename field is not defined in my graphql schema.

I tried to fix by adding the addTypename: false field to my apollo client but it didn't change anything (note I am using apollo boost, that may be why it is not sworking) :

const client = new ApolloClient({
    uri: Meteor.absoluteUrl('graphql'),
    addTypename: false,
    request: operation =>
        operation.setContext(() => ({
            headers: {
                authorization: Accounts._storedLoginToken()
            }
        }))
})

Also it seems than even if it worked it is not very optimized. It seems to me very problematic that a field is added to the query results and I am surprised not to find any clear solution online. Some proposed solution where :

  • filter manually on client side
  • add middleware to apollo
  • add the __typename field to all my schemas...

but none of them seem to fit the 'simplcity' apollo is suppose to bring for queries. I hope there is a simpler, more logical solution provided, but so far, could not find any.

Advocate answered 18/10, 2018 at 12:12 Comment(0)
N
7

Even if using apollo-client and not apollo-boost, you shouldn't set addTypename to false unless you have a compelling reason to do so. The __typename field is used by the InMemoryCache to normalize your query results, so omitting it will likely lead to unexpected behavior around caching.

Unfortunately, there is no "silver bullet" to this problem. Requesting a query and then using that query's data as the variable to some other query could be construed as misusing the API. The Type returned by a query and the Input Type used as an argument are completely different things, even if as Javascript objects they share one or more fields. Just like you can't use types and input types interchangeably within a schema, there shouldn't be an expectation that they can be used interchangeably client-side.

That also means that if you're finding yourself in this situation, you may want to take a second look at your schema design. After all, if the data exists on the server already, it should be sufficient to pass in an id for it and retrieve it server-side, and not have to pass in the entire object.

If you're using some query to populate one or more inputs and then using the value of those inputs inside a mutation, then you're presumably already turning the initial query data into component state and then using that in your mutation. In that scenario, __typename or any other non-editable fields probably shouldn't be included as part of component state in the first place.

At the end of day, doing these sort of manipulations will hopefully be the exception, and not the rule. I would create some kind of helper function to "sanitize" your input and move on.

function stripTypenames (value) {
    if (Array.isArray(value)) {
        return value.map(stripTypenames)
    } else if (value !== null && typeof(value) === "object") {
      const newObject = {}
      for (const property in value) {
          if (property !== '__typename') {
            newObject[property] = stripTypenames(value[property])
          }
      }
      return newObject
    } else {
      return value
    }
}
Nicolanicolai answered 18/10, 2018 at 12:51 Comment(4)
Daniel, thanks again for a fast and clear answer. I am indeed reusing the data in component state, modifying them if needed, and then sending them back, therefore I have to filter them as you mention so I guess the solution is the right solution. However I am really doubtful about graphQL/Apollo about how this problem is handled. For an API that is supposed to go straight to the point and give just the data requested, adding a field to every single object and subobject seems overkill and against the whole point. And lot of github issue request seem to rejoin this opinion.Advocate
The reason __typename is added is to enable normalizing the cache. Check out the normalization section of the (docs)[apollographql.com/docs/react/advanced/…. If all your queries were network-only or you wanted to implement your own dataIdFromObject function, then you could get away with setting addTypename to false.Nicolanicolai
I'm not saying it's right or wrong, but Apollo makes certain assumptions about how an API will be used based on convention. This is reflected in the way the Query and Mutation components function differently -- even though a query and a mutation could both do the same thing, there's an assumption that queries will fetch data and mutations will mutate data. Similarly, I believe, there's an assumption that a query's data will not be used as input for another query, as this would be unconventional, even if it's technically possible in some cases.Nicolanicolai
If someone is interested in how I solved it : I used the omit function from lodash for every object (and subobject) to remove the '__typename' key right after I query my data from apollo and before setting them to the react state. therefor I can then modify them and send again the react state to a Mutation to save new data if needed.Advocate
D
0

From here. '__typename' can be removed with the following helper function

const cleanedObject = omitDeep(myObject, "__typename")

const omitDeep = (obj, key) => {
    const keys = Object.keys(obj);
    const newObj = {};
    keys.forEach((i) => {
      if (i !== key) {
        const val = obj[i];
        if (val instanceof Date) newObj[i] = val;
        else if (Array.isArray(val)) newObj[i] = omitDeepArrayWalk(val, key);
        else if (typeof val === 'object' && val !== null) newObj[i] = omitDeep(val, key);
        else newObj[i] = val;
      }
    });
    return newObj;
  };

const omitDeepArrayWalk = (arr, key) => {
  return arr.map((val) => {
    if (Array.isArray(val)) return omitDeepArrayWalk(val, key)
    else if (typeof val === 'object') return omitDeep(val, key)
    return val
  })
}
Dick answered 16/2, 2019 at 4:38 Comment(1)
How to use this code with apollo client and should we enable the field called addTypeName as trueIncantation
I
0

You shouldn't remove the __typename. They are for the cache and also needed for union types. Probably should wait for an update from apollo. Unfortunately I am also have issue now and I couldn't find a proper solution. First I used to simply delete all the type names, but now I have issue with the union types.

Instantly answered 29/10, 2020 at 8:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.