-
-
Notifications
You must be signed in to change notification settings - Fork 265
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
Remove double hydration #499
Remove double hydration #499
Conversation
@@ -201,8 +206,7 @@ export const createWrapper = <S extends Store>(makeStore: MakeStore<S>, config: | |||
|
|||
const store = useMemo<S>(() => initStore<S>({makeStore}), []); | |||
|
|||
useHybridHydrate(store, initialState); | |||
useHybridHydrate(store, initialStateFromGSPorGSSR); | |||
useHybridHydrate(store, initialStateFromGSPorGSSR ?? initialState ?? 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.
Both are needed, because _app
may have getInitialProps
and page may have getServerSideProps
. This have to result in 2 hydrates.
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.
One should be sufficient.
In the callback of wrapper.getInitialAppProps
you dispatch a number actions filling your store.
After that inside wrapper.getServerSideProps
you can dispatch a next set of actions, you can even use the store.getState() together with some selectors to base logic on for further dispatches. These will all get picked up by the store instance and be reduced after the getInitialAppProps
resulting reduces.
After all of this (getInitialProps and sequentially getServerSideProps) in your _app.tsx JSX you call wrapper.useWrappedStore
which will perform the hydration. At this point your pageProps.initialState
will also have the initialState
(if not altered by the GSSP dispatches), it's basically includes both.
If you don't render a GSSP page, you will fallback to the initialState from _app.tsx' getInitialProps
You can check and see all demo app packages still work as expected with this change.
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 dug around trying to figure out exactly what the data flow is so here are my findings, which corroborate @karaoak's comment. I wrote this as detailed as I did mostly for myself so that I have a reference to fall back on, but hopefully it explains the idea well!
The data flow roughly goes like this:
- getInitialProps (GIP) ->
- getServerSideProps (GSSP) ->
- data passed into the App component as an object containing a
pageProps
key, and in our case ainitialState
key, and some other Next.js stuff.
Let's say we refresh the page. And let's say we have 2 slices in our store state, one "page" slice for our page, one "generic" slice that gets set on each page through GIP.
- When we run GIP, we run the dispatches synchronously on the server. This changes the state in the store. We then return the props as "pageProps", and the store state as "initialState" (I JSON stringified an example object):
{
"pageProps": {
"foo": "bar"
},
"initialState": {
"generic": {
"data": {
"appName": "Test App" // <-- set in GIP dispatches
}
},
"page": {
"data": null // <-- not set yet
}
}
}
We see the generic slice is set now, and the page slice isn't yet, because GSSP hasn't run yet. We also have the initial props now.
- Now we get to GSSP. When we run it, we run the dispatches in it synchronously as well. This changes the state in the store again. We then return the props with inside it an "initialState" key which is the new store state. This "initialState" has the store data from the GIP dispatches in it, as well as the new data that was just dispatched:
{
"props": {
"name": "foo",
"initialState": { // <-- newest store state
"generic": {
"data": {
"appName": "Test App" // <-- set in GIP dispatches
}
},
"page": {
"data": {
"fullName": "FOO" // <-- is set now
}
}
}
}
}
We see the generic slice is still set, and the page slice has now also been set, because both GIP and GSSP have run. We also have the page props.
- Finally, Next.js does some magic before sending off the data to the App component. It takes the object that GIP returned, takes its "pageProps" key and spreads the "props" from GSSP into it. So the "props" data from GSSP gets copied into the "pageProps" from GIP. However, the "initialState" key from the GIP is left untouched. And also: the "initialState" key from GSSP (with the newest data!) is now inside of "pageProps". So the result of the data examples above is this:
{
"pageProps": {
"foo": "bar", // <-- from GSSP
"name": "foo", // <-- from GIP
"initialState": { // <-- from GSSP
"generic": {
"data": {
"appName": "Test App", // <-- set during GIP
}
},
"page": {
"data": {
"fullName": "FOO" // <-- set during GSSP
}
}
}
},
"initialState": { // <-- the old data from GIP
"generic": {
"data": {
"appName": "Test App", // <-- set during GIP
}
},
"page": {
"data": null // <-- not set because this is old data, from GIP, from before the GSSP dispatches
}
}
}
So "pageProps" now contains the page props, and the store state that contains all the dispatches. We don't need the "initialState" version that's outside of "pageProps", we need the one inside of "pageProps" (so pageProps.initialState
).
Now if we don't have GSSP or getStaticProps (GSP) for a page, then it's another story. In that case no extra data is merged into the GIP data, so "pageProps" will not contain an "initialState" key. In this case we do need the "initialState" key that's outside of "pageProps".
So this means that if pageProps.initialState
exists, we want that store state. If it doesn't exist, then we want the initialProps
store state. And if that doesn't exist we should use null
.
In your code you assign pageProps.initialState
to a variable called initialStateFromGSPorGSSR
and the "initialState" outside "pageProps" is simply called initialState
in your code.
So @karaoak's useHybridHydrate(store, initialStateFromGSPorGSSR ?? initialState ?? null);
line is in fact correct.
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.
Very well done!
Since we have #510 this one can be closed I presume? |
Yep, that's right! |
No description provided.