迁移Apollo Client 3.0后useMutation钩子调用报错及解决办法
I recently migrated my app to Apollo Client 3.0 and hit this frustrating error when trying to use useMutation or useQuery:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component
The error message lists common culprits—mismatched React versions, breaking Hook rules, duplicate React copies—but none of those applied here. I even checked related issues but couldn't find a fix that worked. Eventually, I solved it by using the withApollo higher-order component (HOC) since I was working with class components.
The Problematic Code
I initially tried using useMutation in a function component nested inside my class component, which triggered the error:
import { useMutation } from '@apollo/client'; function LockedBlog() { const [lockedBlog] = useMutation(LOCKED_BLOG); }; class EditBlog extends Component { state = { ...initialState }; }
My Apollo Client 3.0 Setup (index.js)
For reference, here's how I configured Apollo Client at the root of my app:
import React from "react"; import ReactDOM from "react-dom"; import "./light.scss"; import App from "./components/App"; import { ApolloProvider } from '@apollo/client' import { ApolloClient } from '@apollo/client/core'; import {ApolloLink} from 'apollo-link'; import {SubscriptionClient} from 'subscriptions-transport-ws'; import {WebSocketLink} from 'apollo-link-ws'; import {createHttpLink} from 'apollo-link-http'; import {InMemoryCache } from '@apollo/client/cache'; import { onError } from 'apollo-link-error'; const errorLink = onError(({ networkError, graphQLErrors }) => { if (graphQLErrors) { graphQLErrors.map(({ message, locations, path }) => console.log( `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`, ), ); } if (networkError) console.log(`[Network error]: ${networkError}`); }); const middlewareLink = new ApolloLink((operation, forward) => { operation.setContext({ headers: { authorization: localStorage.getItem('token') || null } }); return forward(operation); }); const wsLink = new WebSocketLink( new SubscriptionClient("ws://localhost:4004/graphql", { reconnect: true, }), ); const httpLink = middlewareLink.concat( createHttpLink({ uri: "http://localhost:4004/graphql", }) ); const hasSubscriptionOperation = ({query: {definitions}}) => { return definitions.some(({kind, operation}) => kind === 'OperationDefinition' && operation === 'subscription'); } const link = ApolloLink.split( hasSubscriptionOperation, wsLink, httpLink, errorLink, ); const client = new ApolloClient({ link, cache: new InMemoryCache(), }); ReactDOM.render( <React.Fragment> <ApolloProvider client={client}> <App /> </ApolloProvider> </React.Fragment>, document.getElementById("root") );
Dependencies (package.json snippet)
"@apollo/client": "^3.0.2", "@apollo/react-hooks": "^3.1.5", "@material-ui/core": "^4.10.2", "react": "^16.13.1", "react-dom": "^16.13.1"
The Solution: Using withApollo HOC
Since React Hooks (like useMutation) can't be used inside class components, the fix was to inject the Apollo Client instance directly into my class component using the withApollo HOC. Here's the working implementation:
import { withApollo } from '@apollo/client/react/hoc'; import { gql } from '@apollo/client'; // Don't forget this import! class EditBlog extends Component { componentDidMount() { this.lockedBlog(true); } componentWillUnmount() { this.lockedBlog(false); } lockedBlog = locked => { const { id } = this.props.match.params; this.props.client .mutate({ variables: { id, locked }, mutation: gql` mutation($id: ID!, $locked: Boolean!) { lockedBlog(id: $id, data: { locked: $locked }) { locked } } ` }) .then(result => { console.log(result); }) .catch(error => { console.log(error); }); }; // Add your other class methods and render logic here... } // Wrap the component to inject the Apollo Client via props export default withApollo(EditBlog);
Key Takeaways
- Hooks are for function components only: You can't use
useMutationoruseQuerydirectly inside class components—this is a core React Hook rule. withApollobridges class components and Apollo Client: This HOC passes the Apollo Client instance to your class component viathis.props.client, letting you callmutate()orquery()directly.- Ensure your app is wrapped in
ApolloProvider: This was already correctly set up in my index.js, but double-check that your root component is wrapped to provide the client to all child components.
内容的提问来源于stack exchange,提问作者cnsvnc

