Apollo-client: Error while executing Mutation : One of the sources for assign has an enumerable key on the prototype chain.

Created on 20 Nov 2018  ·  12Comments  ·  Source: apollographql/apollo-client

Intended outcome:


The Apollo client is expected to execute a Mutation successfully!
Actual outcome:
Error I am getting:


How to reproduce the issue:
Apollo Client is instantiated like this:

const endpoint = "http://localhost:3000/graphql"


const httpLink = new HttpLink({
    uri: endpoint
});


const request = async (operation) => {
    const token = await AsyncStorage.getItem('token');
    if (!token) {
        return operation.setContext({
            headers: {
            }
        });
    }
    return operation.setContext({
        headers: {
            authorization: `Bearer ${token}`
        }
    });
};

const requestLink = new ApolloLink((operation, forward) =>
  new Observable(observer => {
    let handle;
    Promise.resolve(operation)
      .then(oper => request(oper))
      .then(() => {
        handle = forward(operation).subscribe({
          next: observer.next.bind(observer),
          error: observer.error.bind(observer),
          complete: observer.complete.bind(observer),
        });
      })
      .catch(observer.error.bind(observer));

    return () => {
      if (handle) handle.unsubscribe();
    };
  })
);


const cache = new InMemoryCache();

export default Client = new ApolloClient({
    link: concat(requestLink, httpLink),
    cache,
    defaultOptions: {
        watchQuery: {
            fetchPolicy: 'network-only',
            errorPolicy: 'ignore',
        },
        query: {
            fetchPolicy: 'network-only',
            errorPolicy: 'all',
        },
        mutate: {
            errorPolicy: 'all'
        }
    }
});

I have a component that wraps the mutation here:

export const LoginMutation = gql`
    mutation LoginUser($email:String!, $password:String!){
        login(
            email: $email,
            password: $password
        ){
            authenticationToken
        }
    }
`;
/**
 * Login Mutation Provider
 * @param {*} props 
 */
export const LoginProvider = (props) => {
    return (
        <Mutation mutation={LoginMutation} variables={props.variables}  children={props.children} />
    )
}

And this is how the Mutation is executed..

export class Login extends Component {
  state = {

    email: "",
    password: ""

  }

  handleButtonclick = (page) => {
    Navigator.navigate(page)
  };

  render() {
    const { email, password } = this.state;
    return (
      <LoginProvider variables={{email, password}}>
        {(login, result) => {
          const { data, loading, error, called } = result;
          if (!called) {
            return (
              <View style={styles.container}>
                <Heading style={styles.heading}>
                  Login
                </Heading>
                {/* {
                  error &&
                  <View>
                    <Text>Error View Here</Text>
                  </View>

                } */}
                <View style={styles.loginContainer}>
                  <Card style={styles.card}>
                    <View>
                      <TextInput
                        style={styles.textinput}
                        placeholder={'Email'}
                        onChangeText={v => this.setState({ email: v})}
                      />
                      <TextInput
                        placeholder={'Password'}
                        onChangeText={v => this.setState({ password: v })}
                        secureTextEntry
                      />
                    </View>
                  </Card>
                  <Button style={styles.forgotButton} styleName="clear">
                    <Text style={styles.forgotPass}>Forgot Password?</Text>
                  </Button>
                  <Button onPress={login} style={styles.loginBtn} styleName="full-width">
                    <Text style={styles.loginBtnText}>LOGIN</Text>
                  </Button>

                  <Button style={styles.fbBtn} styleName="full-width">
                    <Icon style={styles.fbIcon} name="facebook" />
                    <Text style={styles.fbBtnText}>Continue with Facebook</Text>
                  </Button>
                </View>
              </View>
            )
          }
          if (error) {
            console.log(error)
            return <View><Text>Error</Text></View>
          }
          if (loading) {
            return (
              <View style={styles.container}>
                <View style={styles.horizontal}>
                  <ActivityIndicator size="large" color="#41C6A0" />
                </View>
              </View>
            )
          }

          if (data) {

            this.handleButtonclick(screenNames.PROFILE)
          }
        }}
      </LoginProvider>
    );
  }
}

Version

  System:
    OS: macOS 10.14
  Binaries:
    Node: 8.11.3 - ~/.nvm/versions/node/v8.11.3/bin/node
    npm: 5.6.0 - ~/.nvm/versions/node/v8.11.3/bin/npm
  Browsers:
    Chrome: 70.0.3538.102
    Safari: 12.0
  npmPackages:
    apollo-boost: ^0.1.21 => 0.1.21
    apollo-cache-inmemory: ^1.3.10 => 1.3.10
    apollo-client: ^2.4.6 => 2.4.6
    apollo-link-context: ^1.0.9 => 1.0.9
    apollo-link-http: ^1.5.5 => 1.5.5
    react-apollo: 2.3.2-beta.0 => 2.3.2-beta.0

Most helpful comment

The underlying issue is that the onPress callback receives a native event as the first parameter and the mutation also accepts some configuration as the first parameter. Therefore the native event ends up in the mutation logic of Apollo client which causes the issue.

As mentioned above, calling the mutation explicitly without parameters is the solution:

<Button onPress={() => mutation()}>

I don't think this is an issue of Apollo client.

All 12 comments

I got the same issue. Did you ever figure this out?

Something is calling Object.assign(Something.prototype, {}), you can check by using a debugger on the line printed in the error. If you can find the bad arguments passed to Object.assign then fix those the error will go away.

Update react-native to the new version with a better polyfill: https://github.com/facebook/react-native/commit/966bb63a80750757dc7a78d7d1f8960a2d8e25e0#diff-cb5173a6f8d88f7801f5127b94bfc330

Or add @babel/polyfill as described in https://github.com/typeorm/typeorm/issues/2502#issuecomment-428749458.

@pl12133 I did what it says and it worked. Do you know anything as of why does this happen?

@johhansantana I wouldn't be able to say without seeing it in a debugger, but if you look at the old polyfill from react-native it mention's that the Object.assign polyfill is not spec compliant:

// WARNING: This is an optimized version that fails on hasOwnProperty checks
// and non objects. It's not spec-compliant. It's a perf optimization.
// This is only needed for iOS 8 and current Android JSC.

Something is being passed to Object.assign that the polyfill doesn't like, failing the hasOwnProperty checks. To find it in a debugger, you can enable "Pause on exceptions" to find where the bad call is being made.

@pl12133 @darmie

I'm getting this error as well. The mutation are causing this error if the variables are passed through the Mutation Component like this:

<Mutation variables={{value:1}}> { ()=>//.... } </Mutation>

However I do not get this error when the variables are dynamically passed through the mutation function
<Mutation variables={{value:1}}> { (mutation)=>{ <Button onPress={()=>mutation({variables:{value:1}})}> Click Here </Button> } } </Mutation>

Someone says it has to do with this file in react native: https://github.com/facebook/react-native/blob/v0.57.8/Libraries/polyfills/Object.es6.js

for (var key in nextSource) { if (__DEV__) { var hasOwnProperty = Object.prototype.hasOwnProperty; if (!hasOwnProperty.call(nextSource, key)) { throw new TypeError( 'One of the sources for assign has an enumerable key on the ' + 'prototype chain. Are you trying to assign a prototype property? ' + "We don't allow it, as this is an edge case that we do not support. " + 'This error is a performance optimization and not spec compliant.', ); } } target[key] = nextSource[key]; }

I look at the next version v.0.58 and the block above is removed. It probably won't cause any error in v0.58, do you think so?

Related issue: https://github.com/facebook/react-native/issues/16814

Just do:

<Mutation variables={{value:1}}>
  {
       (mutation)=>{
             <Button onPress={()=>mutation()}>
                       Click Here
             </Button>
        }
  }
</Mutation>

Works fine for me.

I can confirm that @laurynas' solution works, also mentioned here https://github.com/apollographql/apollo-client/issues/1217#issuecomment-275842450.

Can also confirm @laurynas works aswell for me,

Confirming @laurynas's solution as well.

Also confirming @laurynas's solution as well.
In my case, this issue appears in Android (react-16.6.3 + react-native-0.57.8 + apollo-2.4.1), but not appears in PC (react-16.8.2 + apollo-2.4.1).

The underlying issue is that the onPress callback receives a native event as the first parameter and the mutation also accepts some configuration as the first parameter. Therefore the native event ends up in the mutation logic of Apollo client which causes the issue.

As mentioned above, calling the mutation explicitly without parameters is the solution:

<Button onPress={() => mutation()}>

I don't think this is an issue of Apollo client.

As mentioned in https://github.com/apollographql/apollo-client/issues/4160#issuecomment-471944187, this is not an Apollo Client issue. Thanks!

Was this page helpful?
0 / 5 - 0 ratings