-
BackgroundI'm trying to implement instant messaging with urql. For this experience to feel good, I want two things: to subscribe to new messages so new messages come in immediately and optimistic updates so messages you send appear in the chat immediately. I'm able to get either one of these (subscription and optimistic updates) working on their own, but when I try to combine them, my app gets stuck in an infinite loop and breaks. I'm sure I'm doing something wrong, but I can't find any documentation on how to implement something like this. I'm using Hasura. What I've tried so farOptimistic updates using a query // GetMessages.js
const GET_MESSAGES = `
query GetMessages($myId: uuid!, $theirId: uuid!) {
messages(
where: {
_or: [
{ from: { _eq: $myId }, to: { _eq: $theirId } }
{ from: { _eq: $theirId }, to: { _eq: $myId } }
]
}
order_by: { sent_at: asc }
) {
id
message
from
to
sent_at
}
}
`;
export default GET_MESSAGES; // urql-client.js
const normalizedCacheExhange = cacheExchange({
updates: {
Mutation: {
insert_messages_one: (result, variables, cache) => {
cache.updateQuery(
{
query: GET_MESSAGES,
variables: {
myId: variables.object.from,
theirId: variables.object.to,
},
},
(data) => {
data.messages.push(result.insert_messages_one);
return data;
}
);
},
},
},
optimistic: {
insert_messages_one: (variables) => {
return {
__typename: "messages",
from: variables.object.from,
id: "temp-id",
message: variables.object.message,
sent_at: new Date(),
to: variables.object.to,
};
},
},
});
const client = createClient({
url,
exchanges: [
dedupExchange,
normalizedCacheExhange,
authExchange({
getAuth,
addAuthToOperation,
didAuthError,
willAuthError,
}),
fetchExchange,
subscriptionExchange({
forwardSubscription,
}),
],
});
export default client; I'm not including the code where I actually send a message, because the infinite loop happens when I try to load messages, before I even try to send a message. Making the query a subscription // When using a query
Mutation: {
insert_messages_one: (result, variables, cache) => {
cache.updateQuery(
{
query: GET_MESSAGES,
variables: {
myId: variables.object.from,
theirId: variables.object.to,
},
},
(data) => {
console.log(data.messages);
data.messages.push(result.insert_messages_one);
return data;
}
);
},
},
// console.log returns
Array [
Object {
"__typename": "messages",
"from": "b99bc981-4ecd-49da-b743-7aa26b43c8de",
"id": "6a6b4d3b-e9ad-4773-80d1-50dccd2f0ab5",
"message": "Hello!",
"sent_at": "2021-05-01T22:56:29.731239+00:00",
"to": "eb0f374f-6f2c-4f16-b00b-d80e384333d8",
},
// more messages... But here's what that same // console.log returns
null
Possible Unhandled Promise Rejection (id: 0):
TypeError: null is not an object (evaluating 'data.messages.push') This convinced me that I probably need to write special update logic for my subscription to tell it how to put its data into the cache, since urql was no longer doing it automatically. Here's what I added to my urql cache configuration: updates: {
Subscription: {
messages: (result, variables, cache) => {
const myId = variables.where._or[0].from._eq;
const theirId = variables.where._or[0].to._eq;
cache.updateQuery(
{ query: GET_MESSAGES, variables: { myId, theirId } },
(data) => {
if (!data) return null;
console.log("infinite loop");
return data;
}
);
},
}, This causes "infinite loop" to print many times in the console, and instead of displaying messages, I get this error: Please let me know what I'm doing wrong. Thank you! PS: const SEND_MESSAGE = `
mutation SendMessage(
$myId: uuid!
$theirId: uuid!
$message: String!
$smallerId: uuid!
$largerId: uuid!
$now: timestamptz!
) {
insert_messages_one(
object: { from: $myId, to: $theirId, message: $message }
) {
id
__typename
message
from
to
sent_at
}
update_user_connections_by_pk(
pk_columns: { user_id_smaller: $smallerId, user_id_larger: $largerId }
_set: { last_contacted_at: $now }
) {
__typename
last_contacted_at
}
}
`; |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
I was able to fix this by using the solution suggested in #2257 . Here's my final code for anyone interested: // GetMessages.js
const GET_MESSAGES = (operationType) => `
${operationType} GetMessages($myId: uuid!, $theirId: uuid!) {
messages(
where: {
_or: [
{ from: { _eq: $myId }, to: { _eq: $theirId } }
{ from: { _eq: $theirId }, to: { _eq: $myId } }
]
}
order_by: { sent_at: asc }
) {
id
message
from
to
sent_at
}
}
`;
export default GET_MESSAGES; // urql-client.js
const normalizedCacheExhange = cacheExchange({
keys: {
user_connections: (data) => data.user_id_smaller + data.user_id_larger,
user_tags: (data) => data.user_id + data.tag_id,
},
updates: {
Subscription: {
messages: (result, variables, cache) => {
const myId = variables.where._or[0].from._eq;
const theirId = variables.where._or[0].to._eq;
cache.updateQuery(
{ query: GET_MESSAGES("query"), variables: { myId, theirId } },
(data) => {
if (!data) return null;
data.messages = result.messages;
return data;
}
);
},
},
Mutation: {
insert_messages_one: (result, variables, cache) => {
cache.updateQuery(
{
query: GET_MESSAGES("query"),
variables: {
myId: variables.object.from,
theirId: variables.object.to,
},
},
(data) => {
data.messages.push(result.insert_messages_one);
return data;
}
);
},
},
},
optimistic: {
insert_messages_one: (variables) => {
return {
__typename: "messages",
from: variables.object.from,
id: "temp-id",
message: variables.object.message,
sent_at: new Date(),
to: variables.object.to,
};
},
},
});
const client = createClient({
url,
exchanges: [
dedupExchange,
normalizedCacheExhange,
authExchange({
getAuth,
addAuthToOperation,
didAuthError,
willAuthError,
}),
fetchExchange,
subscriptionExchange({
forwardSubscription,
}),
],
});
export default client; // Conversation.js
export default function Conversation({ route }) {
const myId = useCurrentUserId();
const theirId = route.params.userId;
const [{ data, fetching: loading, error }] = useQuery({
query: GET_MESSAGES("query"),
variables: { myId, theirId },
});
useSubscription({
query: GET_MESSAGES("subscription"),
variables: { myId, theirId },
});
const messages = data?.messages;
// Rest of component omitted |
Beta Was this translation helpful? Give feedback.
I was able to fix this by using the solution suggested in #2257 . Here's my final code for anyone interested: