relay: [Modern] connection is undefined when using multiple QueryRenderers

I’m using React Router v2.8.1 and have the following simple setup for my app:

ReactDOM.render(
  <Router history={browserHistory}>
    <Route path='/' component={App} />
    <Route path='/create' component={CreatePage} />
  </Router>
  , document.getElementById('root')
)

Both App and CreatePage have as a root a QueryRenderer.

App

const AppAllPostQuery = graphql`
  query AppAllPostQuery {
    viewer {
      ...ListPage_viewer
    }
  }
`

class App extends Component {
  render() {
    return (
      <QueryRenderer
        environment={environment}
        query={AppAllPostQuery}
        render={({error, props}) => {
          if (error) {
            return <div>{error.message}</div>
          } else if (props) {
            return <ListPage viewer={props.viewer} />
          }
          return <div>Loading</div>
        }}
      />
    )
  }
}

ListPage is a FragmentContainer that renders a list of Posts. Here’s how the fragment containers for ListPage and Post are defined with their fragments:

ListPage

export default createFragmentContainer(ListPage, graphql`
  fragment ListPage_viewer on Viewer {
    ...Post_viewer
    allPosts(last: 100, orderBy: createdAt_DESC) @connection(key: "ListPage_allPosts", filters: []) {
      edges {
        node {
          id
          description
          imageUrl
          ...Post_post
        }
      }
    }
  }
`)

Post

export default createFragmentContainer(Post, graphql`
  fragment Post_viewer on Viewer {
    id
  }
  fragment Post_post on Post {
    id
    description
    imageUrl
  }
`)

CreatePage

const CreatePageViewerQuery = graphql`
  query CreatePageViewerQuery {
    viewer {
      id
    }
  }
`

class CreatePage extends React.Component {

  state = {
    description: '',
    imageUrl: '',
  }

  render () {
    return (
      <QueryRenderer
        environment={environment}
        query={CreatePageViewerQuery}
        render={({error, props}) => {
          if (error) {
            return <div>{error.message}</div>
          } else if (props) {
            return (
              <div className='w-100 pa4 flex justify-center'>
                  // ...
              </div>
            )
          }
          return <div>Loading</div>
        }}
      />
    )
  }

  _handlePost = (viewerId) => {
    const {description, imageUrl} = this.state
    console.log(`New Post: `, viewerId)
    CreatePostMutation(description, imageUrl, viewerId, () => this.props.router.replace('/'))
  }

}

Now, when I’m deleting a post and use the updater, everything works as expected and the deleted post gets removed from the store:

updater: (proxyStore) => {
  const deletePostField = proxyStore.getRootField('deletePost')
  const deletedId = deletePostField.getValue('deletedId')
  const viewerProxy = proxyStore.get(viewerId)
  const connection = ConnectionHandler.getConnection(viewerProxy, 'ListPage_allPosts')
  ConnectionHandler.deleteNode(connection, deletedId)
}

However, when adding a new post, the ConnectionHandler can’t seem to find the connection with key ListPage_allPosts:

updater: (proxyStore) => {
  const createPostField = proxyStore.getRootField('createPost')
  const newPost = createPostField.getLinkedRecord('post')
  const viewerProxy = proxyStore.get(viewerId)
  const connection = ConnectionHandler.getConnection(viewerProxy, 'ListPage_allPosts')
  // `connection` is undefined
  ConnectionHandler.insertEdgeAfter(connection, newPost)
}

Since connection is undefined here, I’m getting the following error message in the next line:

RelayConnectionHandler.js:225 Uncaught (in promise) TypeError: Cannot read property 'getLinkedRecords' of undefined

My assumption is that connection can’t be found here because I have two different QueryRenderers - and the one that I’m using in this case (unlike the one for the delete-mutation) doesn’t have the ListPage_allPosts in its tree. Is that correct?

What would be a good way to solve this issue? Can connections be shared across QueryRenderers? Is there a way how this can be solved in combination with React Router?

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 6
  • Comments: 22 (14 by maintainers)

Most upvoted comments

What’s happening here is that, out-of-the-box, Relay Modern doesn’t cache anything that isn’t being rendered.

When you go to <CreatePage>, you unmount <App>, and the GC will expunge the connection from the store, so getConnection will correctly return undefined.

Since in this case, the connection isn’t in the local store any more, you don’t need to update it. The correct way to handle this is to just do:

if (connection) {
  ConnectionHandler.insertEdgeAfter(connection, newPost);
}

In other words, only update the store if there’s anything to update.

I’m have a similar issue with React Router v4

RootRouter.js

<Switch>
  <Route path="/list" component={ListPage}/>
  <Route path="/create" component={CreatePage}/>
</Switch>

ListPage.js use follow query

fragment ListPage_viewer on Viewer {
  list(...) @connection(key: "ListPage_list") {
    edges {
      node {
        id
        ...
      }
    }
  }
}

The scenario is as follows.

  1. Visit /list and click /create link
  2. In /create location call CreateItemMutation mutation
  3. In CreateItemMutation mutation updater can’t update ListPage_list because ListPage unmount
  4. Mutation does not update anything
  5. Call history.push("/list") in onCompleted and return to the /list page, showing only old data

How can i solve this issue? Should i set cacheConfig to { force: true } or use Subscriptions?

That makes sense.

@sibelius However, I’m still wondering, is there a way for a parent QueryRender to not be affected by the inner QueryRender? I’m assuming what’s happening here that when the inner QueryRenderer’s mounts and fetches data that blows away the parent’s fetched data, and the parent renderer gets passed the inner renderer’s data.

I recommend avoiding QueryRenderer inside QueryRenderer, the top one will rerender the below one in the tree causing another fetch