apollographql / apollo-client

:rocket:  A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
https://apollographql.com/client
MIT License
19.35k stars 2.66k forks source link

writeToStore: Missing field __typename #1826

Closed yoneapp closed 7 years ago

yoneapp commented 7 years ago

Intended outcome:

I wanted a warning not to be displayed.

Actual outcome:

A warning was displayed on the console.

writeToStore.js:106 Missing field typename in { "edges": [ { "node": { "id": "2789", "typename": "Shop" },

(anonymous) @ writeToStore.js:106 writeSelectionSetToStore @ writeToStore.js:89 writeFieldToStore @ writeToStore.js:200 (anonymous) @ writeToStore.js:96 writeSelectionSetToStore @ writeToStore.js:89 writeResultToStore @ writeToStore.js:69 replaceQueryResults @ replaceQueryResults.js:13 data @ store.js:133 apolloReducer @ store.js:46 combination @ combineReducers.js:120 dispatch @ createStore.js:165 (anonymous) @ ApolloClient.js:174 (anonymous) @ store.js:18 ObservableQuery.updateQuery @ ObservableQuery.js:223 (anonymous) @ ObservableQuery.js:142 ```

How to reproduce the issue:

I used such a code.

import React from 'react'
import ReactDOM from 'react-dom'

import { ApolloClient, createNetworkInterface, ApolloProvider } from 'react-apollo';

const client = new ApolloClient();

class ShopList extends React.Component {
  render() {
    if (this.props.shops) {
      return (
        <div>
          { this.props.shops.edges.map((edge) =>
            <div key={edge.node.id}>{edge.node.id}</div>
          )}
          <button onClick={this.props.loadMoreEntries} >Load More</button>
        </div>
      )
    } else {
      return (<div>Loading...</div>)
    }
  }
}

import { gql, graphql } from 'react-apollo';

const ShopsQuery = gql`
  query Shops($cursor: String) {
    shops(first: 30, after: $cursor) {
      edges {
        node {
          id
        }
      }
      pageInfo {
        endCursor
        hasNextPage
      }
    }
  }
`;

const AreasWithData = graphql(ShopsQuery, {
  props({ data: { loading, shops, fetchMore } }) {
    return {
      loading,
      shops,
      loadMoreEntries: () => {
        return fetchMore({
          query: ShopsQuery,
          variables: {
            cursor: shops.pageInfo.endCursor
          },
          updateQuery: (previousResult, { fetchMoreResult }) => {
            const newEdges = fetchMoreResult.shops.edges;
            const pageInfo = fetchMoreResult.shops.pageInfo;
            return {
              shops: {
                edges: [...previousResult.shops.edges, ...newEdges],
                pageInfo,
              },
            };
          },
        });
      },
    };
  },
})(ShopList);

ReactDOM.render(
  <ApolloProvider client={client}>
    <AreasWithData />
  </ApolloProvider>,
  document.getElementById('root')
)

Could you advise me on this issue?

helfer commented 7 years ago

Hi @yoneapp, you need to include __typename as follows in the result you return from updateQuery:

              shops: {
                __typename: previousResult.shops.__typename,
                edges: [...previousResult.shops.edges, ...newEdges],
                pageInfo,
              },
yoneapp commented 7 years ago

Thank you for your advice 🙇

natterstefan commented 6 years ago

@helfer how can I include this __typename more "automatically" when I writeQuery for instance from a component (wrapped with withApollo and graphql in the HoC`?

I ask because in this example from your website or in this one on your blog it does not look like you need it, but I get the following error/warning when I do not add it:

Missing field __typename in { ... }

and it does not look like the apollo cache has been updated then. Or do I just have to add it and that's it ^^

smeijer commented 6 years ago

@natterstefan , I've written a withCursorResult myself, because all my cursor based queries have the same structure. I'm able to run a fetchMore-query with someting like:

const withData = graphql(COMMENTS_QUERY, {
  options: ({ blogId, pageSize }) => ({
     variables: {
      blogId,
      cursor: {
        sortBy: 'createdAt',
        sortDir: 'ASC',
        limit: pageSize,
      }
     },
    }),
  },

  props: withCursorResult({ query: COMMENTS_QUERY }),
});

On the server side I have a createCursorResult so that it's also easy to create cursors from mongo collections.

Unfortunately it's not really easy to share that code in a generic reusable way; as I use some specific libraries (partial.lenses and ramda), but if wished for, I can place it (as-is) online as a gist.

cooperka commented 6 years ago

@natterstefan This confused me also :slightly_smiling_face: The difference is that in the examples you linked to, the data is being returned from GraphQL and thus already has __typename defined on it. If you pass in your own custom data, you'll have to add __typename yourself.