Force stop container subscribtion (React+CreateContainer)


#1

How can I manually stop the subscription and clean the collection on the client when you switch route via react-router?

The fact is that if I go to the page where there is a component subscribed to, such as “top 10 news” page, where published all the news, I see, as a container of news component performing a normal search the collection first finds objects with subscription last page.

export default createContainer((props)=>{
    let {limitCount, userId} = props;
    let query = (userId)?{'userId':userId}:{};
    var handle = Meteor.subscribe('news_preview', query,limitCount);
    var posts = Post.find(query, {sort:{createdAt:-1}}).fetch(); //Here, the container finds documents to which another component has been signed.
    var needWait = !!handle.ready()&&!!posts; 
    return {
      needWait:!needWait,
      posts:posts
    }
  }, PostList)

In details. With the logic description

PostListContainer
This is a sample of NewsList component, with infinity scroll and subscribtion container. Is just detect scroll, and pass limitCount to child component.

export default class PostListContainer extends Component{
  constructor(props){
    super(props)
    this.state={
      limitCount:10,
    }
    this.handleScroll = this.handleScroll.bind(this);
  }
  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll, false);
  }
  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll, false);
  }
  handleScroll(event) {
    let documentHeight = $(document).height(); //jquery is bad idea, i know
    let windowHeight = $(window).height();
    let scrollPosition = $(window).scrollTop();
    let isLoading = this.refs.PostList.data.needWait;
    if(scrollPosition+windowHeight>=documentHeight && !isLoading){
      this.uppendSkipCount()
    }

  }
  uppendSkipCount(){
    let limitCount = this.state.limitCount;
    this.setState({
      limitCount:limitCount+5
    });
  }
  render(){
    let userId = (this.props.userId)?this.props.userId:undefined;
    return(
      <div>
        <PostList ref="PostList" limitCount={this.state.limitCount} userId={userId} />
      </div>
    )
  }
}

PostList
This component get properties, subscribe and render child components.

export  class PostList extends Component {
    constructor(props){
      super(props);
    }
    postList(){
      return(
        <Masonry>
          {this.props.posts.map((post)=>(
            <div className="col-xs-12 col-sm-6 col-md-6 col-lg-4" key={post._id}>
              <PostPreview  post={post}/>
            </div>
          ))}
        </Masonry>
      )
    }
    render() {
        let content = (this.props.posts)?this.postList():undefined;
        let loading = (this.props.needWait)?<Loading />:undefined;
        return(
          <div>
            {content}
            {loading}
          </div>
        )
    }
  }

export default createContainer((props)=>{
    let {limitCount, userId} = props;
    let query = (userId)?{'userId':userId}:{};
    var handle = Meteor.subscribe('news_preview', query,limitCount);
    var posts = Post.find(query, {sort:{createdAt:-1}}).fetch();
    var needWait = !!handle.ready()&&!!posts;
    return {
      needWait:!needWait,
      posts:posts
    }
  }, PostList)

And it work goood. But if i will come on the page from page that contain for example BestNews component i will get Posts object from that in first iteration:(

export class BestNews extends Component {
    constructor(props){
      super(props);
    }

    componentWillUnmount(){
     this.props.handle.stop(); / looks like it help
    }
    goToPage(post){
      browserHistory.push(`/news/item/${post._id}`);
    }
    bestPosts(){
      return bestPosts = this.props.posts.map((post)=>{
        return(
          <div key={post._id}>
          <ListItem
            onTouchTap={()=>this.goToPage(post)}
            leftAvatar={<Avatar src={post.author().getAvatar().link()} />}
            rightIcon ={<ActionChromeReaderMode />}
            primaryText={post.title}
            secondaryText={post.description}
            />
          <Divider />
          </div>
        )
      });
    }
    render() {
        let content = (this.props.needWait)?<Loading />:this.bestPosts();
        return (
          <div className="box-margin">
            <Paper zDepth={1}>
                <div className="row middle-xs center-xs">
                    <div className="col-xs-12">
                        <h2 style={{fontWeight:'300'}}>Интересные новости</h2>
                    </div>
                </div>
                <div className="row start-xs">
                    <div className="col-xs-12">
                        <List>
                            <Divider />
                            {content}
                        </List>
                    </div>
                </div>
            </Paper>
          </div>
          )

    }
  }

export default createContainer((props)=>{
    var handle = Meteor.subscribe('best_news');
    var posts = Post.find({}, {sort:{likes:-1}, limit:5}).fetch();
    var needWait = !handle.ready() && !posts;
    return {
      handle:handle,
      needWait:needWait,
      posts:posts
    }
  }, BestNews)

After a moment, the container will complete its membership and will give us the relevant objects …
How can I check that when linking the previous container has stopped its subscription and deleted objects?


#2

I found a solution.

It was obvious. But somehow it seems to me that there is a better one.

In this case, we need to use a child component of the container. With method componentWillUnmount() we can manually stop the subscription.

What is negative? The fact that we need to transfer the subscription object in a child component, and then manually stop the subscription. And to do it in each component, which can cause a similar problem.

The answer to the question - why can not we just wait for the completion of the subscription of the container, and then - to display the content. The answer is - it is possible, but as long as you have not implemented infinite scrolling logic. Otherwise, each time the user scrolls down the content will be lost. As long as the subscription is completed.

It’s funny, but I always thought that the container so stops the subscription. I was wrong?


#3

Any ideas?
I thought it is important, cause CreateContainer can’t work normal with React Router…


#4

Solution with manualy stop subscribtion is not good. You understand why.

Take a look at the pretty good solution. We need to protect themselves from the fact that when you visit a component we can get irrelevant data with other components. Yes type have the same data, but - with the other components. It can play a cruel joke with s. I think you understand why.

At the same time we can not put a cap “loading” every time a user scrolls down and loaded data. Because at the time of subscription, all data will disappear. Option with named subscriptions you too are not satisfied. For example, if you are using Astronomy ORM by Jagi.

So. We need simply to intercept getting values ​​from the container and once finished to record the fact of the first subscription in the state of the component.

export  class PostList extends Component {
    constructor(props){
      super(props);
      this.state={
        firstSubscribtion:false
      }
    }
    componentWillReceiveProps(nextProps){
      if(!this.state.firstSubscribtion && nextProps.handleReady){
        this.setState({
          firstSubscribtion:true
        });
      }
    }
    postList(){
      return(
        <Masonry>
          {this.props.posts.map((post)=>(
            <div className="col-xs-12 col-sm-6 col-md-6 col-lg-4" key={post._id}>
              <PostPreview  post={post}/>
            </div>
          ))}
        </Masonry>
      )
    }
    render() {
        let content = (this.props.posts && this.state.firstSubscribtion)?this.postList():undefined;
        let loading = (this.props.needWait)?<Loading />:undefined;
        return(
          <div>
            {content}
            {loading}
          </div>
        )
    }
  }

export default createContainer((props)=>{
    let {limitCount, userId} = props;
    let query = (userId)?{'userId':userId}:{};
    var handle = Meteor.subscribe('news_preview', query,limitCount);
    var posts = Post.find(query, {sort:{createdAt:-1}}).fetch();
    var needWait = !!handle.ready()&&!!posts;
    return {
      handleReady:!!handle.ready(),
      needWait:!needWait,
      posts:posts
    }
  }, PostList)