Skip to content
This repository has been archived by the owner on Jul 10, 2019. It is now read-only.

Link State breaks after client.resetStore() #156

Closed
stolinski opened this issue Jan 1, 2018 · 11 comments
Closed

Link State breaks after client.resetStore() #156

stolinski opened this issue Jan 1, 2018 · 11 comments

Comments

@stolinski
Copy link

Intended outcome

Calling client.resetStore() after a user logs in or out to reset the entire store. Expected things to work as they do without link state.

Actual outcome

All active link state queries throw errors Uncaught (in promise) TypeError: Cannot read property 'todos' of undefined at resolver (where "todos" is the name of the property) and all future mutations on those stop working without a refresh.

How to reproduce the issue

https://github.com/stolinski/apollo-link-state-todos-resetStorebug

Add some todos and click the reset store button. Both todos and visibilityFilter will error out.

@stolinski
Copy link
Author

Full error message here:

index.js:26 Uncaught (in promise) TypeError: Cannot read property 'todos' of undefined
    at resolver (index.js:26)
    at async.js:152
    at step (async.js:53)
    at Object.next (async.js:34)
    at async.js:28
    at new Promise (<anonymous>)
    at __awaiter (async.js:24)
    at executeField (async.js:139)
    at async.js:93
    at step (async.js:53)
    at Object.next (async.js:34)
    at async.js:28
    at new Promise (<anonymous>)
    at __awaiter (async.js:24)
    at execute (async.js:84)
    at Array.map (<anonymous>)
    at async.js:127
    at step (async.js:53)
    at Object.next (async.js:34)
    at async.js:28
    at new Promise (<anonymous>)
    at __awaiter (async.js:24)
    at executeSelectionSet (async.js:76)
    at graphql (async.js:73)
    at Object.next (index.js:46)
    at SubscriptionObserver.next (zen-observable.js:154)
    at zen-observable.js:479
    at new Subscription (zen-observable.js:103)
    at Observable.subscribe (zen-observable.js:229)
    at index.js:38
    at new Subscription (zen-observable.js:103)
    at Observable.subscribe (zen-observable.js:229)
    at dedupLink.js:44
    at new Subscription (zen-observable.js:103)
    at Observable.subscribe (zen-observable.js:229)
    at QueryManager.js:684
    at new Promise (<anonymous>)
    at QueryManager.fetchRequest (QueryManager.js:682)
    at QueryManager.fetchQuery (QueryManager.js:209)
    at ObservableQuery.refetch (ObservableQuery.js:144)
    at QueryManager.js:541
    at Map.forEach (<anonymous>)
    at QueryManager.getObservableQueryPromises (QueryManager.js:534)
    at QueryManager.resetStore (QueryManager.js:527)
    at ApolloClient.resetStore (ApolloClient.js:147)
    at onClick (App.js:9)
    at HTMLUnknownElement.boundFunc (ReactErrorUtils.js:63)
    at Object.ReactErrorUtils.invokeGuardedCallback (ReactErrorUtils.js:69)
    at executeDispatch (EventPluginUtils.js:83)
    at Object.executeDispatchesInOrder (EventPluginUtils.js:106)

@ShockiTV
Copy link
Contributor

ShockiTV commented Jan 1, 2018

Good point. And as defaults are on withClientState level and not cache, we would need to extract this enhancement outside of withClientState.

Our current solution is using cache as argument and not wrap it nor return the instance, so each link would have to mutate the cache object which is just bad architecture.

I would suggest some function like enhanceCache which would return wrapped cache instance and patch it's init and reset functions by calling their default value and also cache.writeData(someObject);

Or it will accept array of someObject so we can apply few defaults - also for other links which need some default queries/fragments to be inserted.

Or if we define it's arguments as object in shape like

{
  data: [{someObject},{someObject}],
  write: customWrite
}

We would enhance it in a way which would allow us to override also these things. For example current official persist link is mutating cache itself and not just wrapping it. This way it could just enhance it in our helper function.

Not sure if we want to prepare some apollo-client or utils function which can be reused, or we prepare just 1 simple enhancer for our usecase. In both cases I would return new instance so it can be chained if needed.

That would let us use default Link cache pointer and not need to provide it as argument.

const cache = new InMemoryCache(...);
// this will also add the .writeData method or we can define another enhancer to do so
const cacheWithDefaults = enahnceLinkStateCache({cache, defaults});

const stateLink = withClientState({
  resolvers: {
    Mutation: {
      updateNetworkStatus: (_, { isConnected }, { cache }) => {
        const data = {
          networkStatus: { isConnected },
        };
        cache.writeData({ data });
      },
    },
  }
});

const client = new ApolloClient({
  cacheWithDefaults,
  link: ApolloLink.from([
    stateLink,
    new HttpLink()
  ]),
});

I did not check how adding reset() method to withClientState would behave as it would probably still fail refetch of queries with client part. Not sure how we could possibly schedule it to execute between resetStore() and all observed query refetching. And I consider it the bad architecture :-D

@peggyrayzis
Copy link
Contributor

Hey @stolinski, thanks for reporting this! This is happening because we need to populate the cache with the defaults again after calling client.resetStore.

Thanks @ShockiTV for looking into a possible solution. This use case is tricky since the state link has no way to hook into events orchestrated by the client. @jbaxleyiii and I have been discussing an events API for Apollo Client that will make these types of problems non-existent in the future.

For now, we've added a hook to the client that will allow you to register callbacks that will be executed after client.resetStore. I'm going to expose the function that writes the defaults on the state link itself so all you'll have to do is call client.onResetStore(stateLink.writeDefaults) after you initialize the client.

@peggyrayzis
Copy link
Contributor

Forgot to mention the solution is documented here:
https://www.apollographql.com/docs/link/links/state.html#defaults

and here:
https://www.apollographql.com/docs/react/features/cache-updates.html#reset-store

@stolinski
Copy link
Author

I've updated all version and am using client.onResetStore(stateLink.writeDefaults); but resetStore is still breaking local-state.

You can see this in action with.
https://github.com/stolinski/apollo-link-state-todos-resetStorebug

Errors shown are:

VM36321:17 DOMException: Failed to execute 'postMessage' on 'Window': TypeError: Cannot read property 'visibilityFilter' of undefined could not be cloned.
    at ApolloClient.hookLogger [as devToolsHookCb] (<anonymous>:14:14)
    at QueryManager.onBroadcast (http://localhost:3000/static/js/bundle.js:4028:27)
    at QueryManager../node_modules/apollo-client/core/QueryManager.js.QueryManager.broadcastQueries (http://localhost:3000/static/js/bundle.js:5108:14)
    at http://localhost:3000/static/js/bundle.js:4684:31
ApolloError.js:34 Uncaught (in promise) Error: Network error: Cannot read property 'todos' of undefined
    at new ApolloError (ApolloError.js:34)
    at QueryManager.js:218
index.js:2177 Unhandled (in react-apollo:Apollo(Link)) Error: Network error: Cannot read property 'visibilityFilter' of undefined
    at new ApolloError (http://localhost:3000/static/js/bundle.js:5670:28)
    at ObservableQuery../node_modules/apollo-client/core/ObservableQuery.js.ObservableQuery.currentResult (http://localhost:3000/static/js/bundle.js:4184:24)
    at GraphQL.dataForChild (http://localhost:3000/static/js/bundle.js:21968:62)
    at GraphQL.render (http://localhost:3000/static/js/bundle.js:22018:33)
    at finishClassComponent (http://localhost:3000/static/js/bundle.js:30538:31)
    at updateClassComponent (http://localhost:3000/static/js/bundle.js:30515:12)
    at beginWork (http://localhost:3000/static/js/bundle.js:30890:16)
    at performUnitOfWork (http://localhost:3000/static/js/bundle.js:32889:16)
    at workLoop (http://localhost:3000/static/js/bundle.js:32953:26)
    at HTMLUnknownElement.callCallback (http://localhost:3000/static/js/bundle.js:23207:14)
    at Object.invokeGuardedCallbackDev (http://localhost:3000/static/js/bundle.js:23246:16)
    at invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:23103:27)
    at renderRoot (http://localhost:3000/static/js/bundle.js:33031:7)
    at performWorkOnRoot (http://localhost:3000/static/js/bundle.js:33679:24)
    at performWork (http://localhost:3000/static/js/bundle.js:33632:7)
    at requestWork (http://localhost:3000/static/js/bundle.js:33543:7)
    at scheduleWorkImpl (http://localhost:3000/static/js/bundle.js:33397:11)
    at scheduleWork (http://localhost:3000/static/js/bundle.js:33354:12)
    at Object.enqueueForceUpdate (http://localhost:3000/static/js/bundle.js:28915:7)
    at GraphQL../node_modules/react/cjs/react.development.js.Component.forceUpdate (http://localhost:3000/static/js/bundle.js:44632:16)
    at GraphQL.forceRenderChildren (http://localhost:3000/static/js/bundle.js:21936:26)
    at next (http://localhost:3000/static/js/bundle.js:21911:27)
    at Object.handleError [as error] (http://localhost:3000/static/js/bundle.js:21915:32)
    at SubscriptionObserver.error (http://localhost:3000/static/js/bundle.js:52186:19)
    at http://localhost:3000/static/js/bundle.js:4423:82
    at Array.forEach (<anonymous>)
    at Object.error (http://localhost:3000/static/js/bundle.js:4423:33)
    at http://localhost:3000/static/js/bundle.js:4742:38
    at http://localhost:3000/static/js/bundle.js:5115:17
    at Array.forEach (<anonymous>)
    at http://localhost:3000/static/js/bundle.js:5114:18
    at Map.forEach (<anonymous>)
    at QueryManager../node_modules/apollo-client/core/QueryManager.js.QueryManager.broadcastQueries (http://localhost:3000/static/js/bundle.js:5109:22)
    at http://localhost:3000/static/js/bundle.js:4684:31

@stolinski
Copy link
Author

Apparently adding a query of any kind to your state resolvers fixes this. As mergebandit mentioned on Slack. None of the examples or docs show anything about a query being needed. I can't imagine needed a Query: () => ({}), is the intended functionality though.

@moseslucas
Copy link

moseslucas commented May 22, 2018

How about with apollo-boost ?
I'm using apollo-boost^0.1.4 what callback should I put in client.onResetStore( ? ) since there's no stateLink when configuring with apollo-boost

import React, { Component } from 'react'
import Route from './src/components/Route'
import { Platform, AsyncStorage } from 'react-native'
import ApolloClient from 'apollo-boost'
import { ApolloProvider  } from 'react-apollo'
import gql from 'graphql-tag'
import defaults from './src/utils/defaults'

const client = new ApolloClient({
  uri: 'xxxxxl',
  clientState: {
    defaults
  },
  request: async (operation) => {
    const token = await AsyncStorage.getItem('AUTH_TOKEN');
    operation.setContext({
      headers: {
        authorization: token
      }
    })
  }
})

// client.onResetStore( ? )

const App = _ => {
  return (
    <ApolloProvider client={client}>
      <Route/>
    </ApolloProvider>
  )
}

@bonham000
Copy link

bonham000 commented Sep 24, 2018

Same issue as @moseslucas, it's unclear how to enable restoration of default values after resetting the store with apollo-boost.

Right now, I'm getting around this with a custom mutation which re-writes all the default store values, but it would be nice if apollo-boost could do this by default whenever the cache is reset.

@ckreiling
Copy link

I am also having this issue. Looking for a workaround as apollo-boost is so-far very convenient for short-term use case.

@adamalfredsson
Copy link

Same issue here. It seems that link-state fails to reset defaults when executing stateLink.writeDefaults()

@calocan
Copy link

calocan commented Jan 15, 2019

I just ran into the same error after following the documentation. Is this indeed closed and fixed or is still an open issue?

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

No branches or pull requests

8 participants