Relay: [Modern] ConnectionHandler can't get connection

Created on 26 May 2017  路  20Comments  路  Source: facebook/relay

Hi folks, yesterday I noticed a strange behavior in my project: somehow my removal mutations stopped working. I couldn't identify the reason of this, neither did any changes in this part of the project.

Here's my mutation:

import { commitMutation, graphql } from 'react-relay'
import { ConnectionHandler } from 'relay-runtime'

const mutation = graphql`
  mutation UserRemoveMutation($input: UserRemoveInput!) {
    UserRemove(input: $input) {
      deletedID
      error
    }
  }
`

function sharedUpdater (store, viewer, deletedID) {
  const viewerProxy = store.get(viewer.id)

  const conn = ConnectionHandler.getConnection(
    viewerProxy,
    'list_users'
  )

  ConnectionHandler.deleteNode(
    conn,
    deletedID
  )
}

function commit (environment, user, viewer, config) {
  return commitMutation(
    environment,
    {
      mutation,
      variables: {
        input: {
          id: user.id
        }
      },
      updater: (store) => {
        const payload = store.getRootField('UserRemove')
        const deletedID = payload.getValue('deletedID')

        sharedUpdater(store, viewer, deletedID)
      },
      optimisticUpdater: (store) => {
        sharedUpdater(store, viewer, user.id)
      },
      onCompleted: config.onCompleted,
      onError: config.onError
    }
  )
}

export default {commit}

And here's the error:

screen shot 2017-05-26 at 8 37 33 am

If I console.log the const conn, it returns me null. I've checked my fragment and the @connection is correct:

viewer: graphql`
  fragment users_list_datatalist_viewer on Viewer {
    id
    users(first: $first, search: $search) @connection(key: "list_users") {
      edges {
        node {
          id
          name
          email
        }
      }
      pageInfo {
        hasNextPage
      }
    }
  }
`

Am I doing something wrong?

Thanks!

Most helpful comment

My guess is that you should do one of these:

@connection(key: "list_users", filters: [])

or:

const conn = ConnectionHandler.getConnection(
    viewerProxy,
    'list_users',
    {search: "a particular query"}
)

All 20 comments

My guess is that you should do one of these:

@connection(key: "list_users", filters: [])

or:

const conn = ConnectionHandler.getConnection(
    viewerProxy,
    'list_users',
    {search: "a particular query"}
)

Does your viewer type have an id field? There is a bug that causes data duplication for the viewer object when it has an id field, it might explain this. Although try @miracle2k's suggestions too!

Just a note:
@connection(key: "list_users", filters: []) means: store the data regardless of the value of search -- this could cause a potential problem if there are two components/views sharing the same connection and when the second view fetches the connection with search: "foo", it will overwrite the data fetched with search: bar in the first view.

@miracle2k
The first one makes sense in my case, and worked like a charm... Thanks!

@josephsavona
Yeah, the viewer type does have an id. Adding filters: [] to @connection fixed my problem... Anything I could do to help, if this issue match? Or may I close it? Thanks!

@JenniferWang
For now I don't have any components sharing connections, so, I don't think it's gonna be a problem. Thanks!

I am having a problem getting a value for ConnectionHandler.getConnection(). Perhaps the concept of viewer is blowing my mind, but I can't figure out what value is supposed to be the first argument.

updater: (store) => {
  const payload = store.getRootField('addComment');
  const newComment = payload.getLinkedRecord('commentEdge');
  const storeRoot = store.getRoot();
  const connection = ConnectionHandler.getConnection(storeRoot, 'Comments_results');
  ...
}

Here's my @connection: https://github.com/staylor/wp-relay-app/blob/modern-relay/src/components/Comments/Comments.js#L18

Mutation: https://github.com/staylor/wp-relay-app/blob/modern-relay/src/components/Comments/Comments.js#L66

As you can see, I have no Viewer. This is all confusing. Help?

I guess you should take a look at this.

You should try to use and explore some boilerplates like this one too.

@JenniferWang @miracle2k what's the difference between @connection(key: "list_users") and @connection(key: "list_users", filters: []) ? I thought if you skip filters , it defaults to []

@bochen2014 If you don't specify the filters, it defaults to all arguments used, IIRC from reading the source.

thanks @miracle2k
I think you are right. I digged into source and found this
and it seems that @jenniferWang has a unit test covering this.

I have a fragment

  fragment item_viewer on viewer {
     id,
     shopItems(first:$first,last:$last,ShopId:$ShopId,SubCategoryId:$SubCategoryId) @connection(key: "item_shopItems", filters:[]) {
       pageInfo {
        hasNextPage
         endCursor
        }
       edges {
         cursor
           node {
           id
           name
           price
         }
       }
    }
  }

when I do,

const conn = ConnectionHandler.getConnection(this.props.viewerId,'item_shopItems', [])
console.log(conn) // null

the connection handler returns null. Don't know what I'm doing wrong

you need to use @connection directive

its there

Is there a server side config for @connection too or you just have to use it in the fragment?

const storeRoot = store.getRoot();
const connection = ConnectionHandler.getConnection(storeRoot, 'Comments_results');

I think you need to use your storeRoot, instead of this.props.viewerId

tried that already, returns undefined. Why store.getRoot when I'm using viewer?

I am having the same issue as i am getting "connection" as undefined if i do :
const storeRoot = store.getRoot();
const connection = ConnectionHandler.getConnection(storeRoot, 'Comments_results');

Any Idea how to proceed?. I am stuck here as my list is not getting updated in UI

Can u share more code? Which node is the parent of this connection?

This is my mutation:

mutation CreateChatMutation($input: CreateChatInput!){
  createChat(input: $input){
    chat{
      id
      from
      content
      createdAt
    }
  }
}

This is my updater.

{
updater: proxyStore => {
        // 1 - retrieve the `newPost` from the server response
          const createPostField = proxyStore.getRootField('createChat')
          const newPost = createPostField.getLinkedRecord('chat')
        // 2 - add `newPost` to the store
          const viewerProxy = proxyStore.getRoot()
          const connection = ConnectionHandler.getConnection(viewerProxy, 'LinkList_allChats', {
        last: 100,
        orderBy: 'createdAt_ASC'
      })
      console.log('updater: connection : ',connection);
          if (connection) {
            ConnectionHandler.insertEdgeAfter(connection, newPost)
          }
}

Is your connection inside Viewer or Query?

If it is inside viewer you need to pass your viewer id, if it is inside query, you need to get ROOT_ID from relay runtime

I am not sure what you meant. I am attaching the code of CreateChat.js. I am also attaching LinkList.js which i am using in CreateChat.js. Can you please elaborate.?

import React, {Component} from 'react'
import CreateChatMutation from './CreateChatMutation'
import {
  QueryRenderer,
  graphql
} from 'react-relay'
import environment from './Environment'
import LinkList from './LinkList'
import Link from './Link'
import NewChatSubscription from './NewChatSubscription'

const CreateChatQuery = graphql`
query CreateChatQuery {
  viewer {
    ...LinkList_viewer
  }
}
`
var refetching = true;

class  CreateChat extends Component {
  state = {
    from:'',
    content:''
  }

  componentDidMount() {
    // Get username form prompt
    // when page loads
    // const {froms, content} = this.state
    const from = window.prompt('username');
    from && this.setState({ from });
    NewChatSubscription()
  }

  render() {

    return (
<div>
<div>
      <QueryRenderer
        environment={environment}
        query={CreateChatQuery}
        variables={{refetch: refetching}}
        render={({error, props}) => {
          if (error) {
            return <div>{error.message}</div>
          } else if (props) {
            return <LinkList viewer={props.viewer} />
          }
          return <div>Loading</div>
        }}
      />
      </div>

      <div>
        <div className='flex flex-column mt3'>
          <input
            className='mb2'
            value={this.state.content}
            onChange={(e) => this.setState({ content: e.target.value })}
            type='text'
            placeholder='Message'
          />
        </div>
        <div
          className='button'
          onClick={() => this._createLink()}>
          submit
        </div>
      </div>
      </div>
    )

  }

  _createLink = () => {
    const {from, content} = this.state
    CreateChatMutation(from, content, (response)=>
    {refetching = false;
      console.log('refetch: '+refetching);
    })

  }
}



export default CreateChat

This is my LinkList.js which i am using in CreateChat.js:

import React, {Component} from 'react'
import Link from './Link'
import {createFragmentContainer, graphql} from 'react-relay'

class LinkList extends Component{
  render() {
    return(
      <div>
      {
        this.props.viewer.allChats.edges.map(({node}) =>
        <Link key={node.__id} link={node}/>
      )}
        </div>
    )
  }
}

export default createFragmentContainer(LinkList, graphql`
fragment LinkList_viewer on Viewer{
  allChats(last: 100, orderBy: createdAt_ASC) @connection(key:"LinkList_allChats", filters:[]){
    edges{
      node{
        ...Link_link
      }
    }
  }
}`)

Was this page helpful?
0 / 5 - 0 ratings