Skip to content

Instantly share code, notes, and snippets.

@amireh
Last active August 29, 2015 14:19
Show Gist options
  • Save amireh/62d1383cfcd7d2cf57d4 to your computer and use it in GitHub Desktop.
Save amireh/62d1383cfcd7d2cf57d4 to your computer and use it in GitHub Desktop.
Different approaches to resolve remote (or "async") props and inject them into React components.

This bit is shared among all approaches, the actual prop definition.

var Actions = require('actions');
var CourseStore = require('stores/CourseStore');

var asyncProps = {
  course: {
    // We load the course from a remote backend and trigger a redirect to
    // a different page if the resource was not found.
    // 
    // Also, we need a routing parameter (`:courseId`) to fetch the resource.
    // 
    // This is handled in an action that dispatches the XHR and interacts with
    // the router to do the redirect if necessary.
    fetch: function(info) {
      return Actions.fetchCourseAndRedirectIfNotFound(info.params.courseId);
    },

    // We don't necessarily want to use the raw version of the payload we
    // received in `#fetch()`; the store has a different version that we
    // should use instead, so we specify the actual value we want to assign
    // to the prop in `#get()`.
    get: function(info) {
      return CourseStore.get(info.params.courseId);
    },

    setup: function(onChange) {
      CourseStore.addChangeListener(onChange);
    },

    teardown: function(onChange) {
      CourseStore.removeChangeListener(onChange);
    }
  }
};

All approaches need to:

  • provide the resolved async props to the component as regular props; the implementation should not be aware of the source of the prop, so that we can easily swap them in/avoid the remote backend when testing or doing funny things like previewing

Approach 1: using an (async) mixin (AsyncPropResolver)

This mixin keeps updating the host component as props are resolved, and provides a hook to test whether any props are still being resolved.

Resolved props are injected/hacked into this.props using a MutableProps implementation.

var CourseHandler = React.createClass({
  mixins: [
    AsyncPropResolver(asyncProps)
  ],

  render() {
    if (this.isResolvingAsyncProps()) {
      return null;
    }
    
    var { course } = this.props;
    var features = {
      manualMessaging: isFeatureEnabled("manual_messaging")
    };

    return (
      <CourseView course={course} features={features} />
    );
  }
});

Pros

  • transparent; this.props gets mutated with the resolved props
  • easily testable; simply pass the prop and we won't have to resolve it
  • the component has access to the loading state and can react to it since it's mounted as props are being resolved

Cons

  • the mutation of this.props is not very appealing

Approach 2: using a (blocking) mixin (BlockingPropResolver)

var CourseHandler = React.createClass({
  mixins: [
    BlockingPropResolver(asyncProps)
  ],

  render() {
    var { course } = this.props;
    var features = {
      manualMessaging: isFeatureEnabled("manual_messaging")
    };

    return (
      <CourseView course={course} features={features} />
    );
  }
});

Pros

  • transparent; this.props gets mutated with the resolved props
  • easily testable; simply pass the prop and we won't have to resolve it
  • is able to stop the transition (and the URL bar from updating) until the props are resolved; if something goes bad, the URL will be left intact

Cons

  • you can't render a loading state (obviously)
  • like AsyncPropResolver, the mutation of this.props is not very appealing

Approach 3: using a <PropResolver /> component

Pass down the asyncProps to this component along with a content renderer and it will resolve the props and pass them to the renderer you specify. Every-time a change occurs, the renderer is re-invoked.

var CourseHandler = React.createClass({
  render() {
    return(
      <PropResolver
        asyncProps={asyncProps}
        params={this.props.params}
        query={this.props.query}
        render={this.renderContent}
      />
    );
  },

  renderContent(props, isResolving) {
    var { course } = props;
    var features = {
      manualMessaging: isFeatureEnabled("manual_messaging")
    };

    return (
      <CourseView course={course} features={features} />
    );
  }
});

Pros

  • doesn't do anything funny like mutating this.props
  • easy to reason about and simple to implement

Cons

  • can not intercept a transition, does not have access to route-handler states/hooks
  • not easy to test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment