Meteor subscription doesn't trigger when refreshing page using React + createContainer

I’ve been using Meteor for a year now and recently switched to React + Flow Router from Blaze + Iron Router.

Anyway, I’m enjoying React quite a bit but I’m having some serious trouble to get my subscription to fire when I refresh my page. If I click on a link that brings me to a specific route, the Subscription works fine. But when I refresh the same page, the ready() function and the subscription in general doesn’t trigger for some reason.

This is my createContainer() code with the subscription:

export default createContainer(function(){
  const subHandle = Meteor.subscribe("iData");
  const isLoading = !subHandle.ready(function(){
    Session.set("subHandle", true)
  });
  let routeCheck = FlowRouter.getParam("id");
  return {
    isLoading,
    questions: Poll.find().fetch(),
  }
}, iSettings)

I have a callback in the ready() function that contains a Session instance, which is then used to tell my loading component to stop showing and show my Settings component. But, like I mentioned above, when navigating to the page by clicking to the settings page, it works fine. But when I’m at the settings page and then refresh the screen, the subscription doesn’t fire at all and the loading screen is permanently showing.

Any help would be appreciated.

1 Like

This is exactly what’s happening to me right now! When I came from another page, it’s ok, but after reloading, it fail…

To solve the problem, you have to create a wrapper for the React component, whose only job is to crab the subscription and then feed it to the component that’s having trouble refreshing.

Post the code for your component and I can help you further if you need it.

1 Like

Thank, here’s my code

import React, {Component} from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Meteor } from 'meteor/meteor';
import Dropzone from 'react-dropzone';

import { Heroes } from '../../api/heroes.js';

import { FormGroup, Input, Form, Col, Row, 
    Card, CardTitle, CardBlock, CardImg, Button, Label } from 'reactstrap';

class ChangeHero extends Component {
    constructor(props) {
        super(props);

        this.state = {
            hero: {}
        }

        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleChange(propertyName, event) {

    }

    handleSubmit(event) {

    }

    // this.setState({hero: this.props.hero})

    render() {
        //console.log(this.state.hero);

        const styles = {
            dropZone: {
                padding: '30px',
                margin: '5px',
                transition: 'all 0.5s',
                borderWidth: '2px', 
                borderColor: 'black', 
                borderStyle: 'dashed', 
                borderRadius: '4px'
            }
        }
        return (
            <Col sm={{ size: 10, offset: 1 }}>
                <Card block>
                    <CardTitle>Change Hero info</CardTitle>
                    <Form onSubmit={this.handleSubmit}>
                        <Row>
                            <Col md="4">
                                <Dropzone style={styles.dropZone} onDrop={this.onDrop} accept="image/*" multiple={false}>
                                    <div>Hero image here, try dropping some files here, or click to select files to upload.</div>
                                </Dropzone>
                            </Col>
                            <Col md="8">
                                <FormGroup>
                                    <Label for="name">Name</Label>
                                    <Input 
                                        id="name" 
                                        name="name"
                                        value={this.props.hero.name ? this.props.hero.name : ''}
                                        onChange={this.handleChange.bind(this, 'name')}
                                        type="text" 
                                        placeholder="Hero's name" />
                                </FormGroup>
                                <FormGroup>
                                    <Label for="universe">Universe</Label>
                                    <Input 
                                        id="universe" 
                                        name="universe"
                                        value={this.props.hero.universe ? this.props.hero.universe : ''}
                                        onChange={this.handleChange.bind(this, 'universe')}
                                        type="text" 
                                        placeholder="Hero's universe" />
                                </FormGroup>  
                            </Col>
                        </Row>
                        <FormGroup>
                            <Label for="description">Description</Label>
                            <Input 
                                id="description" 
                                name="description"
                                value={this.props.hero.description ? this.props.hero.description : ''}
                                onChange={this.handleChange.bind(this, 'description')}
                                type="textarea" 
                                placeholder="Hero's description" />
                        </FormGroup>
                        <Button color="primary" type="submit">Change</Button>
                    </Form>
                </Card>
            </Col>
        );
    }
}

export default createContainer(({ params }) => {
    return {
        hero: Heroes.findOne({_id: params.heroId}) || {}
    }
},ChangeHero);

I’m trying to fix this, if I use the props directly, I can’t type anything…

Ok, this will become your wrapper component:

import ChangeHero from "..."
import React from "react";
import React, {Component} from 'react';
import { Heroes } from '../../api/heroes.js';
import { createContainer } from 'meteor/react-meteor-data';

class ChangeHeroWrapper extends React.Component {
  render(){
  if(this.props.loading){
    return(
      <ChangeHero hero={this.props.hero}/>
    )
  } else{
    // maybe insert a loading component for better UI
  }
  }
}

export default createContainer((params) => {
  const subHandle = Meteor.subscribe("heroes");
  const loading = subHandle.ready();

  return {
    loading: loading,
    hero: Heroes.findOne({_id: params.heroId}) || {}
  }
}, ChangeHeroWrapper);

Notice that you have to subscribe to your Meteor publication in the container. When the subscription is loaded, it will be passed to the ChangeHero component and no matter when you refresh, the data will be loaded appropriately. I don’t know what your subscription is called or if you need a loading component, so you can add or remove as you wish.

Your ChangeHero component will look something like this:

import React, {Component} from 'react';
import { Meteor } from 'meteor/meteor';
import Dropzone from 'react-dropzone';

import { FormGroup, Input, Form, Col, Row, 
    Card, CardTitle, CardBlock, CardImg, Button, Label } from 'reactstrap';

class ChangeHero extends Component {
    constructor(props) {
        super(props);

        this.state = {
            hero: {}
        }

        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleChange(propertyName, event) {

    }

    handleSubmit(event) {

    }

    // this.setState({hero: this.props.hero})

    render() {
        //console.log(this.state.hero);

        const styles = {
            dropZone: {
                padding: '30px',
                margin: '5px',
                transition: 'all 0.5s',
                borderWidth: '2px', 
                borderColor: 'black', 
                borderStyle: 'dashed', 
                borderRadius: '4px'
            }
        }
        return (
            <Col sm={{ size: 10, offset: 1 }}>
                <Card block>
                    <CardTitle>Change Hero info</CardTitle>
                    <Form onSubmit={this.handleSubmit}>
                        <Row>
                            <Col md="4">
                                <Dropzone style={styles.dropZone} onDrop={this.onDrop} accept="image/*" multiple={false}>
                                    <div>Hero image here, try dropping some files here, or click to select files to upload.</div>
                                </Dropzone>
                            </Col>
                            <Col md="8">
                                <FormGroup>
                                    <Label for="name">Name</Label>
                                    <Input 
                                        id="name" 
                                        name="name"
                                        value={this.props.hero.name ? this.props.hero.name : ''}
                                        onChange={this.handleChange.bind(this, 'name')}
                                        type="text" 
                                        placeholder="Hero's name" />
                                </FormGroup>
                                <FormGroup>
                                    <Label for="universe">Universe</Label>
                                    <Input 
                                        id="universe" 
                                        name="universe"
                                        value={this.props.hero.universe ? this.props.hero.universe : ''}
                                        onChange={this.handleChange.bind(this, 'universe')}
                                        type="text" 
                                        placeholder="Hero's universe" />
                                </FormGroup>  
                            </Col>
                        </Row>
                        <FormGroup>
                            <Label for="description">Description</Label>
                            <Input 
                                id="description" 
                                name="description"
                                value={this.props.hero.description ? this.props.hero.description : ''}
                                onChange={this.handleChange.bind(this, 'description')}
                                type="textarea" 
                                placeholder="Hero's description" />
                        </FormGroup>
                        <Button color="primary" type="submit">Change</Button>
                    </Form>
                </Card>
            </Col>
        );
    }
}

Also, in your router file, you have to point the route to the ChangeHeroWrapper and not the ChangeHero component.

1 Like

Finally, thank you so much!