Help, plz! Perhaps someone already faced with my problem....
When re-fetch data with the updated variables -- using relay.setVariables() -- I get the error "Relay was unable to reconcile edges on a connection. This most likely occurred while trying to handle a server response that includes connection edges with nodes that lack anid
field." and Relay storage is not updated although the data come correct.
See code below... (Ruby && ES6)
gemfile
gem 'graphql', '0.16.0'
gem 'graphql-relay', '0.11.2'
....server-side code
node_identification.rb
NodeIdentification = GraphQL::Relay::GlobalNodeIdentification.define do
object_from_id -> (id, ctx) do
type, id = NodeIdentification.from_global_id(id)
case type
when 'FrontApp'
Relay::FrontApp::STATIC
else
Object.const_get(type).find_by(id: id)
end
end
type_from_object -> (obj) do
begin
MODEL_TO_TYPE[obj.class.name.to_sym].constantize
rescue
(obj.class.name + 'Type').constantize
end
end
end
MODEL_TO_TYPE = {
:'Relay::FrontApp' => 'FrontAppType'
}
front_app_query_type.rb
FrontAppQueryType = GraphQL::ObjectType.define do
name 'FrontAppRootQuery'
field :node, field: NodeIdentification.field
field :main, FrontAppType do
resolve -> (obj, args, ctx) {
Relay::FrontApp::STATIC
}
end
end
front_app_type.rb
FrontAppType = GraphQL::ObjectType.define do
name 'FrontApp'
# field :node, field: NodeIdentification.field
interfaces [NodeIdentification.interface]
global_id_field :id
field :tips, TipsType do
argument :filters, types.String
resolve -> (obj, args, ctx) {
filters = args[:filters]
begin
filters = JSON.parse(filters).deep_symbolize_keys!
rescue
filters = nil
end
ctx[:filters] = filters
Relay::Tips::STATIC
}
end
connection :footer, FooterMenuItemType.connection_type do
argument :id, types.ID!
resolve ->(obj, args, ctx){
::Footer.order(:id)
}
end
end
tips_connection_type.rb
TipsConnectionType = TipShowType.define_connection do
field :totalCount, types.Int do
resolve -> (obj, args, ctx) {
obj.object.size
}
end
end
TipsType = GraphQL::ObjectType.define do
name 'Tips'
description 'Tips list for home page'
interfaces [NodeIdentification.interface]
global_id_field :id
connection :mostRecent, TipsConnectionType do
argument :limit, types.Int
resolve ->(obj, args, ctx){
tips = ::Tip.active_users.started(Time.zone.now.in_time_zone(ctx[:current_user] ? ctx[:current_user].time_zone : ::User.get_locally_time_zone).to_date).ready.active.moderated.published.includes(:comments, :tip_type).order('created_at desc').limit(args[:limit])
TipHelpers::Filter.filter(tips: tips, filters: ctx[:filters], reorder: 'created_at desc')
}
end
connection :mostPopular, TipsConnectionType do
argument :limit, types.Int
resolve ->(obj, args, ctx){
tips = ::Tip.active_users.started(Time.zone.now.in_time_zone(ctx[:current_user] ? ctx[:current_user].time_zone : ::User.get_locally_time_zone).to_date).ready.active.moderated.published.includes(:comments, :tip_type).order('(SELECT COUNT(*) FROM comments WHERE tid = tips.id) desc').limit(args[:limit])
TipHelpers::Filter.filter(tips: tips, filters: ctx[:filters], reorder: '(SELECT COUNT(*) FROM comments WHERE tid = tips.id) desc')
}
end
end
/app/models/relay/front_app.rb
module Relay
class FrontApp < Struct.new :id
# HACK::// For relay root queries
STATIC = new(id: 'main').freeze
def initialize *args
opts = args.last.is_a?(Hash) ? args.pop : Hash.new
super *args
opts.each_pair do |k, v|
self.send "#{k}=", v
end
end
def self.find(_)
STATIC
end
end
end
** /app/models/relay/tips.rb**
module Relay
class Tips < Struct.new :id
# HACK:// For relay root queries
STATIC = new(id: 'tips').freeze
def initialize *args
opts = args.last.is_a?(Hash) ? args.pop : Hash.new
super *args
opts.each_pair do |k, v|
self.send "#{k}=", v
end
end
def self.find(_)
STATIC
end
end
end
....and client-side code
class MainApp extends React.Component {
constructor(props) {
super(props);
this.state = {
filters: filtersTemplate
};
this.setFilter = this.setFilter.bind(this);
}
setFilter(filter, value, e) {
if (e) {
e.nativeEvent.stopImmediatePropagation();
e.preventDefault();
}
let { filters } = this.state;
if (['currency', 'sum'].inArray(filter)) {
filters.funds[filter] = value;
} else {
filters[filter] = value;
}
this.setState({
filters: filters
});
this.props.relay.setVariables({filters: filters});
}
render() {
let { tips } = this.props.main;
let renderTipsSection = (section) => {
let tipsCount = tips[section] ? tips[section].edges.length : 0;
let blankCount = 10 - tipsCount;
return (
<ul className="tips__list">
{
tips[section] && tips[section].edges.map(({node}) => (
<li key={node.id} className="tips__list_item">
<TipCard node={node}/>
</li>
))
}
{
[...new Array(blankCount).keys()].map((item, idx) => (
<li key={idx} className="tips__list_item">
<TipCard dummy={true}/>
</li>
))
}
</ul>
);
};
return (
<div>
<div className="wrapper">
<div className="TipsWrapper">
<div className="wrapper">
{ renderTipsSection('mostRecent') }
{ renderTipsSection('mostPopular') }
</div>
</div>
</div>
</div>
);
}}
export default Relay.createContainer(MainApp, {
initialVariables: {
filters: {
category: null,
funds: {
currency: null,
sum: null
},
date: null,
location: null,
browse: null
}
},
prepareVariables: (prevVars) => {
return {
...prevVars,
filters: JSON.stringify(prevVars.filters)
}
},
fragments: {
main: () => Relay.QL`
fragment on FrontApp {
tips(filters: $filters) {
mostRecent(first: 10, limit: 10){
edges {
node {
id
tid
title_name
category
}
}
}
mostPopular(first: 10, limit: 10){
edges {
node {
id
tid
title_name
category
}
}
}
}
}
`
}});
When setFilter() triggered the relay.setVariables will be called... and result...
[RELAY-NETWORK] Run query q3 Object {relayReqId: "q3", relayReqObj: RelayQueryRequest, relayReqType: "query", method: "POST", headers: Object…}
[RELAY-NETWORK] query q3: 3429ms
Warning: Relay was unable to reconcile edges on a connection. This most likely occurred while trying to handle a server response that includes connection edges with nodes that lack an `id` field
After many hours of torment solution of the problem above described was found... see below
SOLUTION
client-side mutation
export default class ApplyFiltersMutation extends Relay.Mutation {
static fragments = {
tips: () => Relay.QL`
fragment on Tips {
id
}
`,
};
getMutation() {
return Relay.QL`mutation {
applyFilters
}`;
}
getVariables() {
return {
filters: this.props.filters
};
}
getFatQuery() {
return Relay.QL`
fragment on ApplyFiltersPayload {
tips
}
`;
}
getConfigs() {
return [
{
type: 'FIELDS_CHANGE',
fieldIDs: {tips: this.props.tips.id},
}
];
}
}
server-side
home_mutations.rb
module HomeMutations
ApplyFilters = GraphQL::Relay::Mutation.define do
name 'ApplyFilters'
input_field :filters, !types.String
return_field :tips, BipsType
resolve -> (args, ctx) {
filters = args[:filters]
begin
filters = JSON.parse(filters).deep_symbolize_keys!
rescue
filters = nil
end
ctx[:filters] = filters
{
tips: Relay::Tips::STATIC
}
}
end
end
tips_type.rb
include TipHelpers::Filter
TipsType = GraphQL::ObjectType.define do
name 'Tips'
description 'Tips list for home page'
interfaces [NodeIdentification.interface]
global_id_field :id
connection :almostRaised, TipsConnectionType do
resolve ->(obj, args, ctx){
TipHelpers::Filter.filter(section: 'almost_raised', filters: ctx[:filters], current_user: ctx[:current_user])
}
end
...
end
end
lib/tip_helpers.rb
class TipHelpers
module Filter
def filter(section:, filters:, current_user:)
...
# p tips.reorder(reorder).to_sql
tips.reorder(reorder)
end
end
end