meteor-utilities / react-list-container

Smart containers for React & Meteor
32 stars 8 forks source link

Any plans or suggestions for no data and/or error handling? #6

Open genyded opened 8 years ago

genyded commented 8 years ago

Right now when no rows match the query for Documents, the default Loading... component displays instead of any children. Do you have any plans to add support for no data and/or error handling? If not, I suppose the earnest is on us to handle that... but not sure how when all we get returned is Loading... if there is no matching data.

SachaG commented 8 years ago

Good point. What would be the best way to handle this? Let the use pass a noResultsComponent to display when there's no data?

Note that for the ListContainer it's a bit different, since you can already test results.length to see if there are results or not.

genyded commented 8 years ago

Yes - List is good to go. For Documents, a no results component could work. It seems like the most important thing is knowing no data was returned though. Once that is there however it's implemented, devs can do whatever they want with that info.

genyded commented 8 years ago

Here is a proposed solution:

import React, { PropTypes, Component } from 'react';

const Subs = new SubsManager();

const DocumentContainer = React.createClass({

  mixins: [ReactMeteorData],

  getMeteorData() {

    // subscribe if necessary
    if (this.props.publication) {
      const subscribeFunction = this.props.cacheSubscription ? Subs.subscribe : Meteor.subscribe;
      this.subscription = subscribeFunction(this.props.publication, this.props.terms);
    }

    const collection = this.props.collection;
    const document = collection.findOne(this.props.selector);

    // look for any specified joins
    if (document && this.props.joins) {

      // loop over each join
      this.props.joins.forEach(join => {

        if (join.foreignProperty) {
          // foreign join (e.g. comments belonging to a post)

          // get the property containing the id
          const foreignProperty = document[join.foreignProperty];
          const joinSelector = {};
          joinSelector[join.foreignProperty] = document._id;
          document[join.joinAs] = collection.find(joinSelector);

        } else {
          // local join (e.g. a post's upvoters)

          // get the property containing the id or ids
          const joinProperty = document[join.localProperty];
          const collection = typeof join.collection === "function" ? join.collection() : join.collection;

          // perform the join
          if (Array.isArray(joinProperty)) { // join property is an array of ids
            document[join.joinAs] = collection.find({_id: {$in: joinProperty}}).fetch();
          } else { // join property is a single id
            document[join.joinAs] = collection.findOne({_id: joinProperty});
          }
        }

      });

    }

    const data = {
      currentUser: Meteor.user(),
      ready: this.subscription.ready()
    }

    data[this.props.documentPropName] = document;

    return data;
  },

  render() {
    const loadingComponent = this.props.loading ? this.props.loading : <p>Loading…</p>
    const noData = noData ? noData : <p>No data found.</p>

    if (this.data.ready) {
      if(!this.data[this.props.documentPropName]) {
        return noData;
      } else {
        if (this.props.component) {
          const Component = this.props.component;
          return <Component {...this.props.componentProps} {...this.data} collection={this.props.collection} />;
        } else {
          return React.cloneElement(this.props.children, { ...this.props.componentProps, ...this.data, collection: this.props.collection });
        }
      }
    } else {
      return loadingComponent;
    }
  }

});

DocumentContainer.propTypes = {
  collection: React.PropTypes.object.isRequired,
  selector: React.PropTypes.object.isRequired,
  publication: React.PropTypes.string,
  terms: React.PropTypes.any,
  joins: React.PropTypes.array,
  loading: React.PropTypes.func,
  noData: React.PropTypes.func,
  component: React.PropTypes.func,
  componentProps: React.PropTypes.object,
  documentPropName: React.PropTypes.string,
  cacheSubscription: React.PropTypes.bool
}

DocumentContainer.defaultProps = {
  documentPropName: "document",
  cacheSubscription: false
}

export default DocumentContainer;
SachaG commented 8 years ago

Thanks! But some of this is going to change anyway as I want to replace getMeteorData with something else. I've been looking at React Komposer, but I can't figure out how to make the component dynamic (see https://github.com/kadirahq/react-komposer/issues/69)

So maybe I'll end up using the "official" createContainer instead.