Closed nibblesnbits closed 4 years ago
Hi @nibblesnbits this weekend i can't stay at the pc so i can only answer you by phone: /
can you give me more info by doing some debugging in the store and here? https://github.com/relay-tools/relay-hooks/blob/master/src/FragmentResolver.ts#L206
When I call loadMore()
I got this in my Relay dev tools.
But I also had a break point on that line and it was never hit. Line 205 was executed and I grabbed this from renderedSnapshot
:
{
"data": {
"appUserId": 0,
"list": {
"totalCount": 38,
"edges": [
{
"node": {
"activityId": 6,
"name": "Virtual Drinks",
"shortDescription": "Whether it's coffee break or happy hour, grab the beverage of your choice for a chemically-enhanced video chat.",
"activityUrl": "https://www.nytimes.com/2020/03/20/well/virus-virtual-happy-hour.html",
"imageUrl": "https://uploads-ssl.webflow.com/5e576c2ceb561c252e1d2e2d/5ec6e38e3e71f1a96e78f940_min-virtual%20coffee.jpg",
"isApproved": true,
"__typename": "Activity"
},
"cursor": "Ng=="
},
{
"node": {
"activityId": 7,
"name": "Cook Mexican Street Tacos with a Pro Chef",
"shortDescription": "Immerse yourself in Mexico's extraordinary street food culture in a guided cooking class with a professional chef.",
"activityUrl": "https://www.airbnb.com/experiences/1661135?source=p2",
"imageUrl": "https://a0.muscache.com/im/pictures/ec6a9398-1ed3-400a-8f97-ee3e7eeed6dd.jpg?aki_policy=exp_md",
"isApproved": true,
"__typename": "Activity"
},
"cursor": "Nw=="
},
{
"node": {
"activityId": 8,
"name": "Meet My Bees",
"shortDescription": "Open a beehive with a fourth-generation beekeeper to see these amazing creatures building honeycombs, making honey, and working together.",
"activityUrl": "https://www.airbnb.com/experiences/1675237?source=p2",
"imageUrl": "https://a0.muscache.com/im/pictures/80add1a5-f051-4aea-a7ee-f5b4491d3346.jpg?aki_policy=exp_md",
"isApproved": true,
"__typename": "Activity"
},
"cursor": "OA=="
},
{
"node": {
"activityId": 9,
"name": "Mobile Photo Secrets with a Nat Geo Winner",
"shortDescription": "Learn the secrets of taking amazing photos with your phone with examples from beautiful Barcelona.",
"activityUrl": "https://www.airbnb.com/experiences/1718920?source=p2",
"imageUrl": "https://a0.muscache.com/im/pictures/lombard/MtTemplate-1718920-media_library/original/b211f9ca-0154-4b7f-9fb3-76757298119e.jpeg?aki_policy=exp_md",
"isApproved": true,
"__typename": "Activity"
},
"cursor": "OQ=="
},
{
"node": {
"activityId": 10,
"name": "Draw from Within with a New York Artist",
"shortDescription": "Re-connect, re-imagine & relax through the restorative power of the creative process.",
"activityUrl": "https://www.airbnb.com/experiences/1655361?source=p2",
"imageUrl": "https://a0.muscache.com/im/pictures/lombard/MtTemplate-1655361-media_library/original/5d1c7ca9-8397-4884-91c1-c5eb0fea7dff.jpg?aki_policy=exp_md",
"isApproved": true,
"__typename": "Activity"
},
"cursor": "MTA="
}
],
"pageInfo": {
"endCursor": "MTA=",
"hasNextPage": true,
"startCursor": "Ng==",
"hasPreviousPage": false
}
}
},
"isMissingData": false,
"seenRecords": {
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer",
"__typename": "AppUser",
"appUserId": 0,
"firstName": "Anonymous",
"lastName": null,
"unviewedActivities(first:5)": {
"__ref": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5)"
},
"__UnviewedActivities_list_connection": {
"__ref": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection"
}
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection",
"__typename": "UnviewedActivitiesConnection",
"__connection_next_edge_index": 5,
"totalCount": 38,
"edges": {
"__refs": [
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:0",
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:1",
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:2",
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:3",
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:4"
]
},
"pageInfo": {
"__ref": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:pageInfo"
}
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:0": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:0",
"__typename": "UnviewedActivitiesConnectionEdge",
"node": {
"__ref": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:0:node"
},
"cursor": "Ng=="
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:0:node": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:0:node",
"__typename": "Activity",
"activityId": 6,
"name": "Virtual Drinks",
"shortDescription": "Whether it's coffee break or happy hour, grab the beverage of your choice for a chemically-enhanced video chat.",
"activityUrl": "https://www.nytimes.com/2020/03/20/well/virus-virtual-happy-hour.html",
"imageUrl": "https://uploads-ssl.webflow.com/5e576c2ceb561c252e1d2e2d/5ec6e38e3e71f1a96e78f940_min-virtual%20coffee.jpg",
"isApproved": true
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:1": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:1",
"__typename": "UnviewedActivitiesConnectionEdge",
"node": {
"__ref": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:1:node"
},
"cursor": "Nw=="
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:1:node": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:1:node",
"__typename": "Activity",
"activityId": 7,
"name": "Cook Mexican Street Tacos with a Pro Chef",
"shortDescription": "Immerse yourself in Mexico's extraordinary street food culture in a guided cooking class with a professional chef.",
"activityUrl": "https://www.airbnb.com/experiences/1661135?source=p2",
"imageUrl": "https://a0.muscache.com/im/pictures/ec6a9398-1ed3-400a-8f97-ee3e7eeed6dd.jpg?aki_policy=exp_md",
"isApproved": true
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:2": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:2",
"__typename": "UnviewedActivitiesConnectionEdge",
"node": {
"__ref": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:2:node"
},
"cursor": "OA=="
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:2:node": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:2:node",
"__typename": "Activity",
"activityId": 8,
"name": "Meet My Bees",
"shortDescription": "Open a beehive with a fourth-generation beekeeper to see these amazing creatures building honeycombs, making honey, and working together.",
"activityUrl": "https://www.airbnb.com/experiences/1675237?source=p2",
"imageUrl": "https://a0.muscache.com/im/pictures/80add1a5-f051-4aea-a7ee-f5b4491d3346.jpg?aki_policy=exp_md",
"isApproved": true
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:3": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:3",
"__typename": "UnviewedActivitiesConnectionEdge",
"node": {
"__ref": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:3:node"
},
"cursor": "OQ=="
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:3:node": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:3:node",
"__typename": "Activity",
"activityId": 9,
"name": "Mobile Photo Secrets with a Nat Geo Winner",
"shortDescription": "Learn the secrets of taking amazing photos with your phone with examples from beautiful Barcelona.",
"activityUrl": "https://www.airbnb.com/experiences/1718920?source=p2",
"imageUrl": "https://a0.muscache.com/im/pictures/lombard/MtTemplate-1718920-media_library/original/b211f9ca-0154-4b7f-9fb3-76757298119e.jpeg?aki_policy=exp_md",
"isApproved": true
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:4": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:edges:4",
"__typename": "UnviewedActivitiesConnectionEdge",
"node": {
"__ref": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:4:node"
},
"cursor": "MTA="
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:4:node": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:unviewedActivities(first:5):edges:4:node",
"__typename": "Activity",
"activityId": 10,
"name": "Draw from Within with a New York Artist",
"shortDescription": "Re-connect, re-imagine & relax through the restorative power of the creative process.",
"activityUrl": "https://www.airbnb.com/experiences/1655361?source=p2",
"imageUrl": "https://a0.muscache.com/im/pictures/lombard/MtTemplate-1655361-media_library/original/5d1c7ca9-8397-4884-91c1-c5eb0fea7dff.jpg?aki_policy=exp_md",
"isApproved": true
},
"client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:pageInfo": {
"__id": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer:__UnviewedActivities_list_connection:pageInfo",
"__typename": "PageInfo",
"hasNextPage": true,
"hasPreviousPage": false,
"endCursor": "MTA=",
"startCursor": "Ng=="
}
},
"selector": {
"kind": "SingularReaderSelector",
"dataID": "client:root:authInfo(redirectUri:\"http://localhost:3000/login\",source:\"google\"):viewer",
"node": {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "cursor",
"type": "ID"
},
{
"defaultValue": 5,
"kind": "LocalArgument",
"name": "count",
"type": "Int!"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "count",
"cursor": "cursor",
"direction": "forward",
"path": ["list"]
}
]
},
"name": "UnviewedActivities_user",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "appUserId",
"storageKey": null
},
{
"alias": "list",
"args": null,
"concreteType": "UnviewedActivitiesConnection",
"kind": "LinkedField",
"name": "__UnviewedActivities_list_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "UnviewedActivitiesConnectionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Activity",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "activityId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "shortDescription",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "activityUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "imageUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "isApproved",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "startCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasPreviousPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "AppUser",
"hash": "e1164886b2d41be49df16b84abc3c0cf"
},
"variables": {
"appUserId": 0,
"count": 10,
"cursor": null,
"redirectUri": "http://localhost:3000/login",
"source": "google"
},
"owner": {
"identifier": "query UnviewedActivitiesRefetchQuery( $appUserId: Int! $count: Int! $cursor: ID) { user(id: $appUserId) { ...UnviewedActivities_user_1G22uz }}fragment UnviewedActivities_user_1G22uz on AppUser { appUserId list: unviewedActivities(first: $count, after: $cursor) { totalCount edges { node { activityId name shortDescription activityUrl imageUrl isApproved __typename } cursor } pageInfo { endCursor hasNextPage startCursor hasPreviousPage } }}{\"appUserId\":0,\"count\":10,\"cursor\":null,\"redirectUri\":\"http://localhost:3000/login\",\"source\":\"google\"}",
"node": {
"fragment": {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "appUserId",
"type": "Int!"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "count",
"type": "Int!"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "cursor",
"type": "ID"
}
],
"kind": "Fragment",
"metadata": null,
"name": "UnviewedActivitiesRefetchQuery",
"selections": [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "id",
"variableName": "appUserId"
}
],
"concreteType": "AppUser",
"kind": "LinkedField",
"name": "user",
"plural": false,
"selections": [
{
"args": [
{
"kind": "Variable",
"name": "count",
"variableName": "count"
},
{
"kind": "Variable",
"name": "cursor",
"variableName": "cursor"
}
],
"kind": "FragmentSpread",
"name": "UnviewedActivities_user"
}
],
"storageKey": null
}
],
"type": "RootQueryType"
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "appUserId",
"type": "Int!"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "count",
"type": "Int!"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "cursor",
"type": "ID"
}
],
"kind": "Operation",
"name": "UnviewedActivitiesRefetchQuery",
"selections": [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "id",
"variableName": "appUserId"
}
],
"concreteType": "AppUser",
"kind": "LinkedField",
"name": "user",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "appUserId",
"storageKey": null
},
{
"alias": "list",
"args": [
{
"kind": "Variable",
"name": "after",
"variableName": "cursor"
},
{
"kind": "Variable",
"name": "first",
"variableName": "count"
}
],
"concreteType": "UnviewedActivitiesConnection",
"kind": "LinkedField",
"name": "unviewedActivities",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "UnviewedActivitiesConnectionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Activity",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "activityId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "shortDescription",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "activityUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "imageUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "isApproved",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "startCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasPreviousPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": "list",
"args": [
{
"kind": "Variable",
"name": "after",
"variableName": "cursor"
},
{
"kind": "Variable",
"name": "first",
"variableName": "count"
}
],
"filters": null,
"handle": "connection",
"key": "UnviewedActivities_list",
"kind": "LinkedHandle",
"name": "unviewedActivities"
}
],
"storageKey": null
}
]
},
"params": {
"id": null,
"metadata": {},
"name": "UnviewedActivitiesRefetchQuery",
"operationKind": "query",
"text": "query UnviewedActivitiesRefetchQuery( $appUserId: Int! $count: Int! $cursor: ID) { user(id: $appUserId) { ...UnviewedActivities_user_1G22uz }}fragment UnviewedActivities_user_1G22uz on AppUser { appUserId list: unviewedActivities(first: $count, after: $cursor) { totalCount edges { node { activityId name shortDescription activityUrl imageUrl isApproved __typename } cursor } pageInfo { endCursor hasNextPage startCursor hasPreviousPage } }}"
},
"hash": "555ab7868d36d6356ff2a4e06048da12"
},
"variables": {
"appUserId": 0,
"count": 10,
"cursor": null,
"redirectUri": "http://localhost:3000/login",
"source": "google"
}
}
}
}
The screenshot is the data I was expecting to be rendered. data
above is the previous data pulled in from the user
prop the component was passed.
I did notice tonight that in https://github.com/relay-tools/relay-hooks/blob/master/src/FragmentResolver.ts#L404, we call getData()
to grab the selected data from the current dataset, then we call changeVariables()
, then another call to getData()
. I'm assuming the idea was that changeVariables()
would update the necessary variables to select the next result, but it doesn't seem to be doing that.
There's a couple obvious things to note here.
payload
argument in that function already contains the correct new result set, so nextData
could become just a selected portion of the payload
argumentresolve()
somewhere in here to make sure everything is up to date. Even when I coerce prevData
into the correct new result, there's still a ton of stuff not being updated to reflect the new data, and from what I can tell resolve()
does a lot of that for us.observer.start
is undefined.Am I at all on the right track? I'm working on finding a way to debug this in the actual TypeScript (npm link
is all I need, I hope), so I may make more progress soon.
@nibblesnbits, reading your code it seems that the problem was the difference between the two queries executed. The first one has as root viewer, the other user.
This function is called by relay when there is an update of the store to notify all the fragments / queries subscribed
I've updated the code to use a refetch, and ensured the queries match, but still no dice. In this case I'm simply asking for a larger result set, but I see the same result.
import PropTypes from "prop-types";
import React from "react";
import { ReactRelayContext, createFragmentContainer } from "react-relay";
import { graphql } from "babel-plugin-relay/macro";
import UnviewedActivities from "./UnviewedActivities";
import { Container, Typography } from "@material-ui/core";
const propTypes = {
authInfo: PropTypes.object.isRequired,
relay: PropTypes.object.isRequired,
};
const contextType = ReactRelayContext;
class Activities extends React.Component {
render() {
const { authInfo } = this.props;
if (!authInfo) {
return <div>Loading...</div>;
}
return (
<Container>
<Typography variant="h6">
Welcome to my app! Select some activities to get started.
</Typography>
<UnviewedActivities authInfo={authInfo} />
</Container>
);
}
}
Activities.propTypes = propTypes;
Activities.contextType = contextType;
export default createFragmentContainer(Activities, {
authInfo: graphql`
fragment Activities_authInfo on AuthInfo {
...UnviewedActivities_authInfo
}
`,
});
import PropTypes from "prop-types";
import React, { useState, useEffect } from "react";
import { graphql } from "babel-plugin-relay/macro";
import ActivityCard from "./ActivityCard";
import { Grid, Button } from "@material-ui/core";
import { useRefetch } from "relay-hooks";
const propTypes = {
authInfo: PropTypes.object.isRequired,
};
const fragmentSpec = graphql`
fragment UnviewedActivities_authInfo on AuthInfo
@argumentDefinitions(
cursor: { type: "ID" }
count: { type: "Int!", defaultValue: 5 }
) {
viewer {
appUserId
list: unviewedActivities(first: $count, after: $cursor)
@connection(key: "UnviewedActivities_list") {
edges {
node {
activityId
name
shortDescription
activityUrl
imageUrl
}
cursor
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
`;
const connectionConfig = {
direction: "forward",
query: graphql`
query UnviewedActivitiesRefetchQuery($count: Int!, $cursor: ID) {
authInfo {
...UnviewedActivities_authInfo
@arguments(cursor: $cursor, count: $count)
}
}
`,
getConnectionFromProps(props) {
return props.list;
},
getFragmentVariables(previousVariables, totalCount) {
return {
...previousVariables,
count: totalCount,
};
},
getVariables({ list }, { count, cursor }) {
return {
count,
cursor: list.pageInfo.endCursor,
};
},
};
const UnviewedActivities = (props) => {
const [authInfo, refetch] = useRefetch(fragmentSpec, props.authInfo);
const [, setLoads] = useState(0);
const [selected, setSelected] = useState([]);
const {
viewer: {
list: { edges },
},
} = authInfo;
useEffect(() => {
const key = "app:selectedActivities";
const item = localStorage.getItem(key);
if (item) {
setSelected(JSON.parse(item));
} else {
localStorage.setItem(key, JSON.stringify([]));
}
}, []);
const loadNextPage = () => {
// if (!hasMore() || isLoading()) {
// return;
// }
refetch(
connectionConfig.query,
{ count: 10 },
null,
() => {
setLoads((v) => v + 1);
console.log("loaded more");
},
{
force: true,
}
);
};
const addActivity = (id, pass) => {
const key = "app:selectedActivities";
const stored = localStorage.getItem(key) || [];
const parsed = stored instanceof Array ? stored : JSON.parse(stored);
const selected = [...parsed, { id, pass }];
localStorage.setItem(key, JSON.stringify(selected));
setSelected(selected);
const remaining = edges.filter(
({ node: a }) => !selected.some((s) => s.id === a.activityId)
);
if (remaining.length === 1) {
loadNextPage();
}
};
const remaining = edges.filter(
({ node: a }) => !selected.some((s) => s.id === a.activityId)
);
return (
<>
<Button onClick={() => loadNextPage()}>Load More</Button>
<Grid
container
direction="row"
justify="center"
alignItems="center"
spacing={2}
>
{remaining.map(({ node: activity }) => (
<Grid item key={activity.activityId}>
<ActivityCard {...activity} addActivity={addActivity} />
</Grid>
))}
</Grid>
</>
);
};
UnviewedActivities.propTypes = propTypes;
export default UnviewedActivities;
refetch()
still makes the correct fetch call and returns the correct data, but still does not update the component.
In this function you can see the relay logics of fragment update notification.
Here you can find all the tests that are performed for usePagination
The refetch/loadMore appears to execute a different query than the one the fragment was subscribed to.
Could you send me the response of the query and the response of the refetch (you can avoid including all the edges).
First response:
{
"data": {
"authInfo": {
"viewer": {
"appUserId": 0,
"list": {
"edges": [
// ...
],
"pageInfo": { "endCursor": "MTA=", "hasNextPage": true }
}
}
}
}
}
Second response:
{
"data": {
"authInfo": {
"viewer": {
"appUserId": 0,
"list": {
"edges": [
// ...
],
"pageInfo": { "endCursor": "MTU=", "hasNextPage": true }
}
}
}
}
}
have you tried debugging In this function?
even better if you can create a minimal example project on github so that i can investigate the error
The fragment is not updated by the Relay store because the query is performed with different parameters. This for Relay is as if a different query had been executed.
to work it is necessary to modify fetchQuery in UnviewedActivities.js
const fetchQuery = graphql`
query UnviewedActivitiesRefetchQuery(
$redirectUri: String!
$source: String!
$count: Int!
$cursor: ID
) {
authInfo(redirectUri: $redirectUri, source: $source) {
...UnviewedActivities_authInfo @arguments(cursor: $cursor, count: $count)
}
}
`;
That works!
But there's one new problem. It's pulling in the new data, but only appending the data, not replacing the entire result set with the new edges. Is this intended?
Pull on the repo to get what I mean.
yes, this is its behavior. Maybe @sibelius can give you some advice for your use case :)
the default behavior of connection
is to append in the end
check the connection handler code, you can modify it to your will
My problem is solved! I guess it was never a bug to begin with. Sorry! 🙏
My Components:
My Problem:
Upon a successful call to
loadMore()
, the fetch is made and I see the correct new set of data in my dev tools Network tab, but theuser
value returned fromusePagination()
is not updated. Even if I force a rerender withsetLoads((v) => v + 1);
, theuser
value has not been updated.My Setup: