Search code examples
reactjsgraphqlapollographql-subscriptions

Why does my data disappear when a subscription goes through?


Why am I getting undefined for data when the subscription goes through? I am trying to figure out where the undefined for my props.data.allLocations is coming from. Any help would be appreciated.

//Higher order component

export const LocationList = graphql(

      gql`
        query ($site: ID!) {
          allLocations(
            filter: {
            site: {
              id:$site
            } 
          }
          ) {
            id
            name
          }
        }
          `,


     {

       options: (ownProps)=>({
         variables: {
           site: ownProps.site
         },
       }),
      //props were injected from the JSX element
      props: props => { 

          return {
            data: props.data,
            subscribeToData: params => {
              return props.data.subscribeToMore({
                document:

                  gql`
                    subscription newLocations {
                      Location {
                        mutation
                        node {
                          id
                          name
                        }
                      }
                    }
                   `,
                variables: {
                  //Empty for now
                  //site: props.site,
                },
                updateQuery: (previousState, {subscriptionData}) => {

                  if (!subscriptionData.data) {
                                return previousState;
                            }

                var newArray =[subscriptionData.data.Location.node].concat(previousState.allLocations)
                var newState = { 
                  ...previousState,
                  allLocations: [
                    {
                      ...subscriptionData.data.Location.node
                    },
                      ...previousState.allLocations
                  ],

                };

                return newState
                }
              })
            },

          }
      },
      onError: (err) => console.error(err)
           } )(EntityList)

//List Component

class EntityList extends Component {


    componentWillReceiveProps(newProps) {
    if (!newProps.data.loading) {
      console.log(newProps)
      if (this.subscription && this.props.hasOwnProperty('subscribeToData')) {
        if (newProps.data.allLocations !== this.props.data.allLocations) {
          console.log("Resubscribe")
          // if the todos have changed, we need to unsubscribe before resubscribing
          this.subscription()
        } else {
          console.log('else')
          // we already have an active subscription with the right params
          return
        }
      }
      this.subscription = this.props.subscribeToData(newProps)

    }}


  render () {

    if (this.props.data.loading) {
      return (<div>Loading</div>)
    }


    var Entities = [];
    for(var key in this.props.data) {

        if(this.props.data[key] instanceof Array){
          Entities = Entities.concat(this.props.data[key])

          //console.log(this.props.data[key])
        }
      }


    return (
      <div className='w-100 flex justify-center bg-transparent db'  >
        <div className='w-100 db' >
          {Entities.map((entity) =>

           ( 
              <EntityListView 
                user={this.props.user} 
                key={entity.id} 
                entityId={entity.id} 
                name={entity.name} 
                profilePic={(entity.profilePic)?entity.profilePic.uuid : this.props.defaultPic.uuid} 
                clickFunc={this.props.clickFunc}
              /> 
            ))} 

        </div>
      </div>
    )
  }
}

Solution

  • Thanks to all who looked into this. I got it to work by splitting up my queries as suggested by @nburk. I was able to keep it modular by creating my own high order component. If anyone is interested:

    const Subscriber = (document, config) => {  
    
      return (WrappedComponent) => { 
    
        return class extends Component {
    
          constructor(props) {
            super(props)
            this.state={}
            this.subscription = null
          }
            componentWillReceiveProps(nextProps) {
              console.log(nextProps)
              if (!this.subscription && !nextProps.data.loading) {
                console.log("Sucess")
                let { subscribeToMore } = this.props.data
                this.subscription = [subscribeToMore(
                {
                  document: document,
                  updateQuery: (previousResult, { subscriptionData }) => {
                    console.log(subscriptionData)
                    var newResult = [subscriptionData.data[config.modelName].node].concat(previousResult[config.propName])
                    console.log(newResult)
                    return { 
                      //"allItems"
                      [config.propName]: newResult,
                    }
    
                  },
                  onError: (err) => console.error(err),
                }
                  )]
                    this.setState({});
                  }
                }
    
    
          render () {
    
            if (this.props.data.loading) {
              return (<div>Loading</div>)
            }
               return (
                      <WrappedComponent {...this.props} />
                     )
    
          }
        }
      }
    }
    
    export default Subscriber
    
    /*prop config = {
      propName: "string", //allItems // from query 
      //so I do not have to parse the gql query (would have to recieve from parent)
      modelName: "string" //Item 
      // from subscribe //so I do not have to parse the gql subscribe
    }
    */
    

    I now wrap my component like this:

    export const LocationList = graphql(gql`..., 
      {options: (ownProps)=>({
        variables: {
          site: ownProps.site
        }})})(
      Subscriber(gql`..., 
        propName:"allLocations",
        modelName:"Location",
      )(EntityList))
    

    Thanks