Max Argent Max Argent - 3 months ago 13
React JSX Question

Relay re-fetching failed... error Relay was unable to reconcile edges on a connection

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 an

id
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

Answer

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