Intended outcome:
Subscription trigger only once.
Actual outcome:
Subscription trigger multiple times
I was doing tutorial with react and apollo on https://www.howtographql.com/react-apollo/8-subscriptions/
The mentioned there bug about subscription triggered multiple times was considered bug on Prisma or Yoga server, but I consider it related to Query component of react-apollo.
I am not sure if that's a misuse of component in the tutorial or bad architecture of Query component, so I decided to post it here for your consideration.
More of the problem is here: https://github.com/prisma/graphql-yoga/issues/101
In my opinion to prevent such behaviour, subscriptions should be registered on component mount and unregistered on component unmount.
How to reproduce the issue:
Follow the tutorial at https://www.howtographql.com/react-apollo/8-subscriptions/
Version
I'm experiencing the same issue here.
As a temporary workaround, I implemented a flag that signaling about either component is already subscriber
subscribeToNewMessages = subscribeToMore => {
if (!this.subscribed){
subscribeToMore({...})
}}
I am also having this issue, changes in the app are causing my
Any updates on this? I don't wanna do a dirty workaround for this
I fixed this by setting data = null after I ran the subscription trigger once, so subsequent re-fires would be a noop, and then once the subscription received new data it would run as expected again.
@audiolion
Not an ideal solution, because you are subscribing for the event on every rendering
@MyroshnykRoman
<Subscription subscription={foo}>
{({ data }) => {
if (data === null) return fallback;
// do something
data = null;
)}}
</Subscription>
we arent resubscribing here
@audiolion the multiple resubscribing, at least in my case, its when I try to subscribe from a Query component with its argument subscribeToMore, and since my Query component is in the render() method, it subscribe multiple times
@miguelacio could you share your code? I think you are forgetting to unsubscribe to the subscription on componentWillUnmount
<Query
query={getMessages}
fetchPolicy={'cache-and-network'}
variables={{ skip: 0, first: 10, conversationID: id }}
>
{({ networkStatus, error, data, fetchMore, subscribeToMore }) => {
if (networkStatus === 1)
return (
<LoadingContainer/>
)
if (error) return <Text>Error</Text>
const { getMessages } = data
this.subscribeToMessages(subscribeToMore)//I subscribe here
return (
<FlatList
ref={scroll => (this.scroll = scroll)}
inverted
data={getMessages}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
ItemSeparatorComponent={this.renderSeparator}
/>
)
}}
</Query>
subscribeToMessages = subscribeToMore => {
const { navigation: { state }, } = this.props
const { params: { id } } = state
subscribeToMore({
document: subscriptionMessages,
variables: { conversationID: id },
updateQuery: (prev, { subscriptionData }) => {
//I do my logic here
},
})
}
since the render is called multiple times, My code subscribe multiple times, and i het multiple subscriptions of the same object, I aprreciate if you would me to the right direction of this @audiolion
using:
"apollo-cache-inmemory": "1.3.9",
"apollo-client": "2.4.5",
"apollo-link-context": "1.0.9",
"apollo-link-http": "1.5.5",
"apollo-link-ws": "^1.0.14",
"react-native": "0.57.8",
@miguelacio yep that is the issue right there.
this.subscribeToMessages(subscribeToMore)//I subscribe here
That line needs to be called outside the render. Create a class that wraps your <FlatList /> and pass the subscribeToMessages as a prop, then subscribe and unsubscribe in the componentDidMount and componentWillUnmount methods. The subscribeToMore call returns a function to unsubscribe from that channel.
class MyList extends React.Component {
componentDidMount() {
this.unsubMessages = this.props.subscribeToMessages(subscribeToMore);
}
componentWillUnmount() {
if (this.unsubMessages) {
this.unsubMessages() // this unsubscribes so if this ever unmounts you dont keep the sub around
}
}
render() {
return <FlatList />
}
}
This should no longer be an issue with current day react-apollo (but definitely let us know if it is). Thanks!
The problem still reproducing (my react-apollo version is 2.5.8). So, explanation of @audiolion is correct: anyone should call subscribeToMore out of render. This is also covered in apollo subscriptions docs, where it is recommended to call it in componentDidMount
@hwillson running into this with @apollo/react-*: 3.1.4, using the same pattern described by @miguelacio above and functional components:
export const TeamChatComponent = (props: Props) => {
return (
<ListTeamChatMessagesComponent variables={...}>
{({ loading, data, error, subscribeToMore, refetch, fetchMore }) => {
subscribeToMore<
OnTeamChatMessageSubscription,
OnTeamChatMessageSubscriptionVariables
>({
document: OnTeamChatMessageDocument,
variables: { ... },
updateQuery: (prev) => {
console.log("got update");
)
}
The console.log is called multiple times, and it keeps adding new iterations on every render, getting exponentially out of control.
Should this be supported in "present day apollo"?
@andreialecu subscribeToMore should only be called once, you are calling it on every render. Follow the example in the Apollo Docs and subscribe on componentDidMount (or React.useEffect if using function components)
const COMMENT_QUERY = gql`
query Comment($repoName: String!) {
entry(repoFullName: $repoName) {
comments {
id
content
}
}
}
`;
const COMMENTS_SUBSCRIPTION = gql`
subscription onCommentAdded($repoName: String!) {
commentAdded(repoName: $repoName) {
id
content
}
}
`;
function CommentsPageWithData({ params }) {
const { subscribeToMore, ...result } = useQuery(
COMMENT_QUERY,
{ variables: { repoName: `${params.org}/${params.repoName}` } }
);
return (
<CommentsPage
{...result}
subscribeToNewComments={() =>
subscribeToMore({
document: COMMENTS_SUBSCRIPTION,
variables: { repoName: params.repoName },
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
const newFeedItem = subscriptionData.data.commentAdded;
return Object.assign({}, prev, {
entry: {
comments: [newFeedItem, ...prev.entry.comments]
}
});
}
})
}
/>
);
}
export class CommentsPage extends Component {
componentDidMount() {
this.props.subscribeToNewComments();
}
}
https://www.apollographql.com/docs/react/data/subscriptions/#subscribetomore
@audiolion yes, I know why it's happening, but your example uses hooks, not query components.
With the Query component calling it just once gets tricky, because you only have access to subscribeToMore once it's rendered. In the mean time I converted the code to use hooks and that's ok.
My comment was mainly referring to @hwillson's mention that it should work properly in newer versions, when it doesn't.
If you use a Query component and pull out subscribeToMore render prop you need to pass it to another component that will call it in a useEffect or componentDidMount.
Most helpful comment
The problem still reproducing (my react-apollo version is 2.5.8). So, explanation of @audiolion is correct: anyone should call subscribeToMore out of render. This is also covered in apollo subscriptions docs, where it is recommended to call it in componentDidMount