-
Notifications
You must be signed in to change notification settings - Fork 786
Conversation
I'm just a bystander with little context, but I imagine performance would be one reason – especially if users defined any heavy |
Sure, but we have to create the instances at some point to render the subtree. Also in this case we are talking about |
// (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.setState = (newState) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asking out of curiosity, why is it necessary to call setState
? I don't really see any relevant usages of setState
within the react-apollo codebase
Feel free not to answer if it's complicated 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't call set state, but it's totally possible that any component we are "rendering" will rely on calling in it componentWillMount
. I think there were some issues about this (and that's why there's a test for it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhh, I see. That makes sense, I believe.
I imagine another option might be to run Component.flushState()
or whatever it's called, just before Component.componentWillMount()
? But I don't necessarily see that as more elegant.
Again, thanks for taking the time to explain!
|
||
// Tell walkTree to not recurse inside this component; we will | ||
// wait for the query to execute before attempting it. | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, asking out of curiosity, no need to respond (I just find this to be a really interesting project):
Won't this prevent queries on child components from executing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but it will eventually get rendered when the query is ready. The assumption is that you have something like
@graphql(...)
const X = ({ loading }) => {
if (loading) {
// A. there's no nested calls to `graphql` inside this render tree
return <Loading/>;
} else {
// B. there could be anything in here
}
}
We don't bother rendering the A. subtree as we are going to await the query setup by graphql
and then render X
with the results (and loading: false
). There are probably pathological cases you can think of where this is inefficient, but I think it seems a reasonable assumption and is "eventually consistent" in any case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, is Component.fetchData
recursive then? What if the B
subtree has a @graphql(...)
of its own?
Thanks very much for taking the time to explain!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getQueriesFromTree
is recursive, so B
's @graphql
s will get picked up after X
's query is loaded.
We have to wait for X
's query though, as any sub-queries may rely on the results.
Thanks very much for taking the time to explain!
No problem! Happy to talk about it while I still remember ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhh, of course. Somehow I missed the fact that there are two separate recursions happening: one to find all top-level graphql
nodes, and one to find/execute their child queries once the first have completed... although, of course, you had clearly stated that.
We have to wait for X's query though, as any sub-queries may rely on the results.
Of course. Presumably in the future some introspection could check whether this is true, and batch the queries together if not.
Hah, well I'm glad to get your thoughts on the public record while we can then! 😄
let fieldsToNotShip = ['minimizedQuery', 'minimizedQueryString']; | ||
for (let field of fieldsToNotShip) delete apolloState.queries[queryId][field]; | ||
} | ||
.then(() => ReactDOM.renderToString(component)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, what a massive cleanup!
Are you planning to keep initialState
as a return value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, see point 1. in the original PR comment; it was a bit of a hack IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, I see...
the user will have access to an ApolloClient anyway, so can't they just get the store themselves
Yes, that sounds much better anyway 😄
This PR seems like a major step forward in the simplicity and readability of this functionality! |
@tmeasday awesome work! I'm going to try and review this today! |
@tmeasday sorry I was away on holiday. This PR is great indeed. As for my issue the problems was setting props and context to the instance of the component because the properties could not be modified. setState was not an issue for me. So this should fix my scenario. |
Totally agree with this. |
Speaking as a (potential) user of this library, perf is likely to be a top consideration in whether to adopt Apollo for SSR. I don't know very much about the tradeoffs at hand, so I wouldn't take my "opinion" on the matter too seriously. But If tests can be written to combat the brittleness and detect breakages, that'd be much preferred to a slower-but-more-"correct" approach. Writing some benchmarks might be helpful in making that call as well (though again, I don't have the context necessary to tell how helpful that'd be). |
@tmeasday this looks awesome. I'm great to merge it if you are! (Feel free to merge and release if you want!) |
I think it's a step forward in any case, so I am going to merge it. I guess we'll hear about problems as it goes live ;) |
@jbaxleyiii I wonder what your thoughts are on
|
@tmeasday it was a hack 👍 I think its great |
Also, thank you SO much for the fantastic work with the new SSR renderer! |
Ok, I'll update the docs etc to reflect the change. |
Refactored SSR to use a slightly more principled
walkTree
method.Tests pass, some points of interest:
context
from the SSR functions. This seemed kind of undefined (what was it? The lowest context we saw in the tree? Should we have been just picking the firstApolloProvider
child context we saw?). All in all it seemed a bit hacky, and the user will have access to anApolloClient
anyway, so can't they just get the store themselves?I am still assigning
instance.setState
. This seems to not throw an exception. How does this fit your understanding @rewop? I was surprised by this but we need to do something like this to allow people to callsetState
incomponentWillMount
(or pursue a different method of rendering[1]).@jbaxleyiii I wonder if rather than passing
{ element, context }
around (and callingelement.type.fetchData
, a static method), we could not just pass theinstance
around, and callinstance.fetchData
? It would make the code a little simpler inserver.js
, but also it would makegraphql.js
a bit simpler too. Is there a reason to not instanciate the element on the server before running the query?[1] I feel like ultimately we should be trying to use React's rendering infrastructure, as although this is no doubt quicker, it feels really brittle and wouldn't be surprised if there's lots of things you can do that break it.