Hacker News new | ask | show | jobs
by brandonbloom 3165 days ago
This is a classic static/dynamic problem. Luckily, in the case of React, there may be a really good solution that covers most necessary use cases. First, an analogy:

Consider Ruby's method_missing and corresponding respond_to? methods [1]. method_missing may respond to a method dynamically, but respond_to? should return true if a method will be responded to. If you constrain yourself to normal methods, or carefully defined method_missing bodies, you can predict with 100% accuracy, but what if you do something like this?

    class Whoops
      def method_missing(method)
        if method == :fancy and Random.rand > 0.5
          "it's your lucky day!"
        else
          super(method)
        end
      end

      def respond_to?(method, include_private = false)
        if method == :fancy
          true # Well, actually, "maybe?".
        else
          super
        end
      end
    end
Temporarily ignoring the problem created by randomness: Implementing respond_to? methods is annoyingly redundant. In order to fully-automate the process of implementing respond_to?, you'd need to analyze a method_missing implementation. In the common case, this is static type inference. In the limit case, this is arbitrary abstract interpretation.

However, effectful code, such as this random example, is provably impossible to analyze correctly! You need to have some conservative approximation. Returning true from respond_to? for the :fancy method means that respond_to? should actually be called might_respond_to?.

OK, now back to React and GraphQL: Luckily, over-fetching data is usually not that big a deal (as long as you're within bandwidth constraints). It's often desirable to! Since you might need that data soon when the view changes. Knowning this, it's safe to assume a method for assembling a GraphQL query from fragments can be conservative. That's essentially what a totally static, manual composition of query fragments does. If that's a tight enough approximation for you (like some other commenters have implied) then great! But what if it's not?

Luckily, virtual DOMs are dramatically simpler and less powerful than arbitrary method_missing implementations. If your render method is already pure, you can call it an extra time to use it as its own abstract interpretation. It will produce a virtual DOM, which you can then analyze yourself. For example:

    let capture = (f) => {
      let oldCreateElement = React.createElement;
      try {
        React.createElement = (component, props, ...children) => {
          return {component, ...props, children};
        };
        return f();
      } finally {
        React.createElement = oldCreateElement;
      }
    };

    class DynamicQuery extends React.Component {
      graphql() {
        let dom = capture(() => this.render());
        return gatherQuery(dom);
      }
      render() {
        ...
      }
    }
Here, the gatherQuery method will walk the objects returned by our monkey-patched createElement method, composing query fragments along the way. By following the component references, you can find static methods (easy!), or even instantiate components to do recursive renders yourself (this gets hairy, since React doesn't really make any of this public API). You can do something pretty simple with this: Just hard code a few component types to make some limited dynamic decisions, fallback to static over-query beyond that. Or you can get really fancy and do recursive rendering and analysis, if you really want to.

[1] http://blog.enriquez.me/2010/2/21/dont-forget-about-respond-...

1 comments

That would be a similar approach to the `getDataFromTree` function that Apollo offers: http://dev.apollodata.com/react/server-side-rendering.html#g...

But it's way more work than just passing fragments up the tree via `context` in `componentWillMount` or `componentDidMount`, which is relatively simple by comparison. :)