next-with-apollo
Version:
Apollo HOC for Next.js
164 lines • 7.56 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = require("react");
function getProps(element) {
return (element.props ||
element.attributes);
}
function isReactElement(element) {
return !!element.type;
}
function isComponentClass(Comp) {
return (Comp.prototype && (Comp.prototype.render || Comp.prototype.isReactComponent));
}
function providesChildContext(instance) {
return !!instance.getChildContext;
}
// Recurse a React Element tree, running visitor on each element.
// If visitor returns `false`, don't call the element's render function
// or recurse into its child elements
function walkTree(element, context, visitor) {
if (Array.isArray(element)) {
element.forEach(function (item) { return walkTree(item, context, visitor); });
return;
}
if (!element)
return;
// a stateless functional component or a class
if (isReactElement(element)) {
if (typeof element.type === 'function') {
var Comp = element.type;
var props = Object.assign({}, Comp.defaultProps, getProps(element));
var childContext = context;
var child = void 0;
// Are we are a react class?
// https://github.com/facebook/react/blob/master/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js#L66
if (isComponentClass(Comp)) {
var instance_1 = new Comp(props, context);
// In case the user doesn't pass these to super in the constructor
instance_1.props = instance_1.props || props;
instance_1.context = instance_1.context || context;
// set the instance state to null (not undefined) if not set, to match React behaviour
instance_1.state = instance_1.state || null;
// Override setState to just change the state, not queue up an update.
// (we can't do the default React thing as we aren't mounted "properly"
// however, we don't need to re-render as well only support setState in
// componentWillMount, which happens *before* render).
instance_1.setState = function (newState) {
if (typeof newState === 'function') {
// React's TS type definitions don't contain context as a third parameter for
// setState's updater function.
// Remove this cast to `any` when that is fixed.
newState = newState(instance_1.state, instance_1.props, instance_1.context);
}
instance_1.state = Object.assign({}, instance_1.state, newState);
};
// this is a poor man's version of
// https://github.com/facebook/react/blob/master/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js#L181
if (instance_1.componentWillMount) {
instance_1.componentWillMount();
}
if (providesChildContext(instance_1)) {
childContext = Object.assign({}, context, instance_1.getChildContext());
}
if (visitor(element, instance_1, context) === false) {
return;
}
child = instance_1.render();
}
else {
// just a stateless functional
if (visitor(element, null, context) === false) {
return;
}
child = Comp(props, context);
}
if (child) {
if (Array.isArray(child)) {
child.forEach(function (item) { return walkTree(item, context, visitor); });
}
else {
walkTree(child, childContext, visitor);
}
}
}
else {
// a basic string or dom element, just get children
if (visitor(element, null, context) === false) {
return;
}
if (element.props && element.props.children) {
react_1.Children.forEach(element.props.children, function (child) {
if (child) {
walkTree(child, context, visitor);
}
});
}
}
}
else if (typeof element === 'string' || typeof element === 'number') {
// Just visit these, they are leaves so we don't keep traversing.
visitor(element, null, context);
}
// TODO: Portals?
}
exports.walkTree = walkTree;
function hasFetchDataFunction(instance) {
return typeof instance.fetchData === 'function';
}
function isPromise(query) {
return typeof query.then === 'function';
}
function getQueriesFromTree(_a, fetchRoot) {
var rootElement = _a.rootElement, _b = _a.rootContext, rootContext = _b === void 0 ? {} : _b;
if (fetchRoot === void 0) { fetchRoot = true; }
var queries = [];
walkTree(rootElement, rootContext, function (element, instance, context) {
var skipRoot = !fetchRoot && element === rootElement;
if (skipRoot)
return;
// console.log('IS', !!instance, isReactElement(element));
if (instance && isReactElement(element) && hasFetchDataFunction(instance)) {
console.log('x', element);
var query = instance.fetchData();
if (isPromise(query)) {
queries.push({ query: query, element: element, context: context });
// Tell walkTree to not recurse inside this component; we will
// wait for the query to execute before attempting it.
return false;
}
}
});
return queries;
}
// XXX component Cache
function getDataFromTree(rootElement, rootContext, fetchRoot) {
if (rootContext === void 0) { rootContext = {}; }
if (fetchRoot === void 0) { fetchRoot = true; }
var queries = getQueriesFromTree({ rootElement: rootElement, rootContext: rootContext }, fetchRoot);
// no queries found, nothing to do
if (!queries.length)
return Promise.resolve();
var errors = [];
// wait on each query that we found, re-rendering the subtree when it's done
var mappedQueries = queries.map(function (_a) {
var query = _a.query, element = _a.element, context = _a.context;
// we've just grabbed the query for element, so don't try and get it again
return query
.then(function (_) { return getDataFromTree(element, context, false); })
.catch(function (e) { return errors.push(e); });
});
// Run all queries. If there are errors, still wait for all queries to execute
// so the caller can ignore them if they wish. See https://github.com/apollographql/react-apollo/pull/488#issuecomment-284415525
return Promise.all(mappedQueries).then(function (_) {
if (errors.length > 0) {
var error = errors.length === 1
? errors[0]
: new Error(errors.length + " errors were thrown when executing your GraphQL queries.");
error.queryErrors = errors;
throw error;
}
});
}
exports.default = getDataFromTree;
//# sourceMappingURL=getDataFromTree.js.map