-
Notifications
You must be signed in to change notification settings - Fork 20
Add automatic SSR data hydration support #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
'root', | ||
renderToString( | ||
store.collectData(<App />) | ||
) |
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.
propper 4 space indentation please
lib/DataHydrator.js
Outdated
}, | ||
|
||
load(optns) { | ||
const defaults = { |
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.
specify defaults outside the function itself
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.
Also, why 3s for self-destructing ? Isn't that unsafe ?
lib/SSRDataStore.js
Outdated
import PropTypes from 'prop-types'; | ||
import { EJSON } from 'meteor/ejson'; | ||
import { generateQueryId } from './utils.js'; | ||
export const SSRDataStoreContext = React.createContext(null); |
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.
This release is going to break React 15.x, we need to make it optional. We still want to allow latest versions of grapher-react to be used with 15.x.
lib/SSRDataStore.js
Outdated
} | ||
} | ||
|
||
export default class SSRDataStore{ |
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.
use consistent Coding standards (space before {)
lib/withReactiveQuery.js
Outdated
const data = query.fetch(); | ||
// Check the SSR query store for data | ||
if(Meteor.isClient && window && window.grapherQueryStore) { | ||
const ssrData = DataHydrator.getQueryData(query); |
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.
I now understand why you are doing the self-destruct thingie. But a healthier approach would be to set a flag once first hydration has been made, and do the check in here. I don't know something like window.grapher.hydrationCompleted = true
And pls use consistent styles if[SPACE](...)
lib/withStaticQuery.js
Outdated
|
||
// Check the SSR query store for data | ||
if(Meteor.isClient && window && window.grapherQueryStore) { | ||
const data = DataHydrator.getQueryData(query); |
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.
create 2 additional methods for this, there's too much code coupled in the constructor.
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.
what if the query gets an error ? how is this treated ?
withQuery.js
Outdated
config | ||
}) | ||
return ( | ||
<SSRDataStoreContext.Consumer> |
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.
I fail to understand why you need context, if you can use window
as a global variable to do your thing.
window.GRAPHER = { queryStore: {}, hydrationCompleted: bool, dataStore }
Hello @abecks good job and great initiative, I really enjoyed reviewing the code. However I have the following concerns: How do we address security ? The biggest flaw we now expose is for named queries, that may get filtered based on the current user. In order for this to be solved, we need to have a solution (not in this package) that automatically translates auth token as cookie, on server we read and we translate it to an userId, and then it can get passed to the .fetch(context). Example: namedQuery.fetch({ userId: 'XXX'})
normalQuery.fetch({ userId: 'XXX'}) This ensures that the firewalls are called and emulates a sort of client request. My next concern is the self-destruction after 3s. I think I left a comment where we can set a flag to verify if the data has passed the initial rehydration. And we can simply destroy the store then. |
lib/SSRDataStore.js
Outdated
getScriptTags() { | ||
const data = this.store.getData() | ||
|
||
return `<script type="text/grapher-data">${this.encodeData(data)}</script>` |
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.
use text/grapher-data, as a constant variable somewhere. Let's not rely on strings for things that are re-used in multiple places.
Hey @theodorDiaconu , Thanks for taking the time to do such a thorough review. Regarding the code consistency, do you have an Using React's Context API: I'm using the context on the server, it isn't necessary on the client. On the server it acts as a per-request store. It's possible we could move all of this functionality of
Self destructing the store: We can manually destroy the store when hydration is finished, but it is hard to know when hydration is actually finished. If all of the React components are loaded on initial render, we can do something like this: await DataHydrator.load()
ReactDOM.hydrate(<App />, document.getElementById('root'))
DataHydrator.destroy() The problem here is that any dynamically imported components will load after the store has been destroyed. I guess at this point the user has an option to delay the Security: Almost forgot! Thanks for reminding me. I can implement a similar method to what is used in
|
@abecks let's add prettier with 2 space, single-quote, and trailling-comma=es5 and it's fine by me. Regarding security, we need to offer a recipe in the documentation for it to work, it won't be part of grapher-react package. Regarding the context, there might be a better way I have to think about it. |
Hey @theodorDiaconu , I've made the following improvements:
Usage:On the client: import { DataHydrator, User } from 'meteor/cultofcoders:grapher-react'
import { Tracker } from 'meteor/tracker'
Meteor.startup(async () => {
await DataHydrator.load()
Tracker.autorun(c => {
if (!Meteor.loggingIn()) {
c.stop()
ReactDOM.hydrate(
<User>
<App />
</User>,
document.getElementById('root')
)
DataHydrator.destroy()
}
})
}) In the future I can expand the DataHydrator to inject the User object as well, so we won't have to wait for On the server: import { SSRDataStore, User } from 'meteor/cultofcoders:grapher-react'
onPageLoad(async sink => {
const store = new SSRDataStore()
sink.renderIntoElementById(
'root',
renderToString(
store.collectData(
<User token={sink.request.cookies.meteor_login_token}>
<App />
</User>
)
)
)
const storeTags = store.getScriptTags()
sink.appendToBody(storeTags)
}) |
Hey @theodorDiaconu, any further feedback on this? |
@abecks very nice job! |
I think I need more time to analyze it I feel that bringing in meteor auth config in this package is too much. What if we split it and have something like: grapher-react-ssr (and include optional authentication modules in there) ? |
Thanks for the feedback. It definitely makes sense to spin off the auth functionality into something like
I can work on making these modifications if you think we are on the right track. |
@abecks yes I believe that is the right path. And I will feature your packages in documentation for SSR but it's advisable that you have your own README.md on each package. Cheers! |
@abecks sorry for my unresponsiveness here, any updates? I am interested in having a solution for this, and it would be a shame not to use the amazing work you did so far! |
ping @abecks |
Sorry for the lack of response. Will be busy for the next few weeks.
I've hit a pretty big snag, in that collection transforms are not currently
run for hydrated data on the client. I want to see if I can come up with a
solution for this before I release anything.
|
Target issue: cult-of-coders/grapher#199
Adds data hydration support for server-side rendering.
Hey @theodorDiaconu
I'd love to get your opinion on this approach:
Uses a similar approach to
styled-components
. Creates a new data store for every request, and uses the new React Context API to expose it to allwithQuery
components.withStaticQuery
andwithReactiveQuery
contain logic to immediately fetch a query result, store it, and retrieve it again on the client.FastRender
uses, however it may require modification of grapher core (I haven't looked yet). Static Queries are not loaded twice.grapher
core in any way.There are no tests for this repository, so I wasn't able to run any, but I did test static, reactive, and singular queries pretty extensively.