Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relay should not attempt to diff plural fields with two-or-more linkedIDs when those IDs are client IDs #1243

Closed
GrigoryPtashko opened this issue Jun 27, 2016 · 7 comments

Comments

@GrigoryPtashko
Copy link

GrigoryPtashko commented Jun 27, 2016

Hello.

Two days of experiments and no result. Please, any advice.
Relay is up-to-date - 0.9.1.
Long story short. I'm making a recursive classifier. The next level
opens on mouse over.
The problem is that the child categories stay null in Relay but
my grapql actually answers with edges.

I'm going to give the complete code here.
I've noticed one thing that I think is the reason but I don't know how to fix it.

Here's the schema

type Category implements Node {
  id: ID!
  name: String
  hasChildren: Boolean
  categories(before: String, after: String, first: Int, last: Int): CategoryConnection
}

type CategoryConnection {
  edges: [CategoryEdge]
  pageInfo: PageInfo!
}

type CategoryEdge {
  node: Category
  cursor: String!
}

interface Node {
  id: ID!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

type Query {
  category(id: ID!): Category
  node(id: ID!): Node
}

Here's the component which must render a category and fetch dependent categories on mouse over. It is pretty much a general recursive component I suppose.

import React from 'react';
import Relay from 'react-relay';


class CategoryRoute extends Relay.Route {
  static queries = {
    category: () => Relay.QL`
        query {
            category(id: $id)
        }
    `,
  };

  static paramDefinitions = {
    id: { required: true },
  };

  static routeName = 'CategoryRoute';
}

class CategoryItemSubCategoryList extends React.Component {
  static propTypes = {
    currentLevel: React.PropTypes.number.isRequired,
    maxLevel: React.PropTypes.number,
    activeCats: React.PropTypes.array,
    elementSelectedCallback: React.PropTypes.func,

    categories: React.PropTypes.object,
  };

  render() {
    const currentLevel = this.props.currentLevel;
    const maxLevel = this.props.maxLevel;
    const elementSelectedCallback = this.props.elementSelectedCallback;

    return (
      <ul className="dropdown-menu" aria-labelledby="dLabel">
          {this.props.categories.map(({ node }) => (
            currentLevel < maxLevel && node.hasChildren ?
              <li><CategoryItemContainer key={node.id}
                                         category={node}
                                         maxLevel={maxLevel}
                                         currentLevel={currentLevel + 1}
                                         activeCats={this.props.activeCats}
                                         elementSelectedCallback={elementSelectedCallback}/></li> :
              <li>{elementSelectedCallback ?
                <a onClick={elementSelectedCallback.bind(this, node.id)}>{node.name}</a> :
                <a href={`search?c=${node.id}`}>{node.name}</a>
              }</li>
          ))}
      </ul>
    );
  }
}

const CategoryItemSubcategoryListContainer = Relay.createContainer(CategoryItemSubCategoryList, {
  fragments: {
    categories: () => Relay.QL`
        fragment on Category {
            categories(first: 10000) {
                edges {
                    node {
                        ${CategoryItemContainer.getFragment('category')}
                    }
                }
            }
        }
    `,
  },
});

class CategoryItem extends React.Component {
  static propTypes = {
    category: React.PropTypes.object.isRequired,
    currentLevel: React.PropTypes.number.isRequired,
    maxLevel: React.PropTypes.number,
    activeCats: React.PropTypes.array,
    elementSelectedCallback: React.PropTypes.func,

    relay: React.PropTypes.object,
  };

  static getDefaultProps = {
    maxLevel: 100,
    activeCats: [],
  };

  render() {
    const category = this.props.category;
    const currentLevel = this.props.currentLevel;
    const maxLevel = this.props.maxLevel;
    const elementSelectedCallback = this.props.elementSelectedCallback;

    return (
      <div className="list-group-item" onMouseOver={this.openCategory.bind(this)}>
        {elementSelectedCallback ?
          <a onClick={elementSelectedCallback.bind(this, category.id)}>{category.name}
            {currentLevel < maxLevel && category.hasChildren ?
              <span className="glyphicon glyphicon-menu-right" aria-hidden="true"></span> : ''}</a> :
          <a href={`search?c=${category.id}`}>{category.name}
            {currentLevel < maxLevel && category.hasChildren ?
              <span className="glyphicon glyphicon-menu-right" aria-hidden="true"></span> : ''}</a>
        }

        {/* Выводим список подкатегорий */}
        {currentLevel < maxLevel && category.hasChildren && this.props.relay.variables.expanded &&
          <CategoryItemSubcategoryListContainer key={category.id}
                                                categories={category.categories}
                                                currentLevel={currentLevel}/>
        }
      </div>
    );
  }

  openCategory() {
    this.props.relay.setVariables({ expanded: true });
  }
}

const CategoryItemContainer = Relay.createContainer(CategoryItem, {
  initialVariables: {
    expanded: false,
  },

  fragments: {
    category: (variables) => Relay.QL`
        fragment on Category {
            id
            name
            hasChildren

            ${CategoryItemSubcategoryListContainer.getFragment('categories').if(variables.expanded)}
        }
    `,
  },
});

export default class Classifier extends React.Component {
  static propTypes = {
    rootCategoryId: React.PropTypes.string.isRequired,
    maxLevel: React.PropTypes.number,
    title: React.PropTypes.string,
    activeCats: React.PropTypes.array,
    elementSelectedCallback: React.PropTypes.func,
  };

  render() {
    return (
      <Relay.RootContainer
        Component={CategoryItemContainer}
        route={new CategoryRoute({ id: this.props.rootCategoryId })}
        renderFetched={(data) => <CategoryItemContainer {...data}
                                                        currentLevel={0}
                                                        maxLevel={this.props.maxLevel}
                                                        title={this.props.title}
                                                        activeCats={this.props.activeCats}
                                                        elementSelectedCallback={this.props.elementSelectedCallback}/>}
      />
    );
  }
}

And now here's what happens. During the first render of the Classifer Relay issues the query:

query Classifier($id_0:ID!) {
  category(id:$id_0) {
    id,
    ...F0
  }
}
fragment F0 on Category {
  id,
  name,
  hasChildren
} 

I see the root category. Everything is OK. Then I hover over the category item and Relay makes another query to fetch the connection - categories. Here's the query

query Classifier_CategoryRelayQL($id_0:ID!) {
  node(id:$id_0) {
    ...F2
  }
}
fragment F0 on Category {
  id,
  name,
  hasChildren
}
fragment F1 on Category {
  _categories2slDfj:categories(first:10000) {
    edges {
      node {
        id,
        ...F0
      },
      cursor
    },
    pageInfo {
      hasNextPage,
      hasPreviousPage
    }
  },
  id
}
fragment F2 on Category {
  id,
  ...F1
} 

And here's a part of the response:

{
    "data": {
        "node": {
            "id": "101510000566",
            "_categories2slDfj": {
                "edges": [
                    {
                        "node": {
                            "id": "101510000170",
                            "name": "Оружие",
                            "hasChildren": false
                        },
                        "cursor": "c2ltcGxlLWN1cnNvcjA="
                    },

And here's the problem. After I make this.props.relay.setVariables({ expanded: true }) there is no property categories in the this.props.category! But I think it must be there! The backend server returns data since you can see the query is issued and it is correct.
One thing that I don't understand is why there is this alias _categories2slDfj:categories(first:10000)?
Maybe this is the reason?

Please, any advice.. I'm so close to production. This is the only thing that's holding me.

Thank you.

PS I made a gist with Classifier.js. Here it is https://gist.github.com/GrigoryPtashko/9689476a61058dc844ade11f9b81298a

@steveluscher
Copy link
Contributor

Thanks for your question! I think have an answer for you, but let's take this to Stack Overflow before we continue.

We want to make sure to keep signal strong in the GitHub issue tracker – to make sure that it remains the best place to track issues that affect the development of Relay.

Questions like yours deserve a purpose-built Q&A forum. Would you like to post this question to Stack Overflow with the tag #relayjs? We'll be happy to answer there. Post a link to your Stack Overflow question here, to so that we don't lose track of it.

https://stackoverflow.com/questions/ask?tags=relayjs

@GrigoryPtashko
Copy link
Author

@steveluscher
Copy link
Contributor

This is actually a bug in Relay. When we diff the query with the data in the store, we only use the first item in a plural field (the assumption being that all others will be the same). I'll send a fix shortly after lunch.

@steveluscher steveluscher reopened this Jun 27, 2016
@steveluscher steveluscher changed the title Recursive fetching of a connection is rejected by Relay (possibly another reason here). No ideas left.. Relay should not attempt to diff plural fields with two-or-more linkedIDs when those IDs are client IDs Jun 27, 2016
@josephsavona
Copy link
Contributor

Awesome job debugging, @steveluscher!

@steveluscher
Copy link
Contributor

Fixed in 28a8008.

@GrigoryPtashko
Copy link
Author

@steveluscher can you, please, look at my comment here http://stackoverflow.com/questions/38059070/recursive-fetching-of-a-connection-is-rejected-by-relay-possibly-another-reason/38065217#comment63570782_38065217 ?
In short - nothing changed. I've tried both ways - recursion on connection and on list field. I still don't know what to do.

@steveluscher
Copy link
Contributor

Hi @GrigoryPtashko. I saw your comment on SO, but your question is probably about to be closed as a duplicate. Feel free to open a separate question if you're still having trouble.

In answer to your comment: it looks to me like the diff bug is fixed, and now you have an undefined categories prop. Find out why that's undefined, and you should be able to make it a little further.

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

No branches or pull requests

3 participants