Skip to content

Commit 2e24c19

Browse files
authored
Merge pull request #1835 from reduxjs/feature/ssr-support
2 parents ce83bf8 + ed8fdc8 commit 2e24c19

File tree

7 files changed

+59
-28
lines changed

7 files changed

+59
-28
lines changed

package.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"coverage": "codecov"
4141
},
4242
"peerDependencies": {
43-
"react": "^18.0.0-beta"
43+
"react": "^18.0.0-rc"
4444
},
4545
"peerDependenciesMeta": {
4646
"react-dom": {
@@ -52,15 +52,11 @@
5252
},
5353
"dependencies": {
5454
"@babel/runtime": "^7.12.1",
55-
"@testing-library/react-12": "npm:@testing-library/react@^12",
5655
"@types/hoist-non-react-statics": "^3.3.1",
5756
"@types/use-sync-external-store": "^0.0.3",
5857
"hoist-non-react-statics": "^3.3.2",
59-
"react-17": "npm:react@^17",
60-
"react-dom-17": "npm:react-dom@^17",
61-
"react-is": "^18.0.0-beta-fdc1d617a-20211118",
62-
"react-test-renderer-17": "npm:react-test-renderer@^17",
63-
"use-sync-external-store": "1.0.0-beta-fdc1d617a-20211118"
58+
"react-is": "^18.0.0-rc.0",
59+
"use-sync-external-store": "^1.0.0-rc.0"
6460
},
6561
"devDependencies": {
6662
"@babel/cli": "^7.12.1",
@@ -81,6 +77,7 @@
8177
"@testing-library/jest-dom": "^5.11.5",
8278
"@testing-library/jest-native": "^3.4.3",
8379
"@testing-library/react": "13.0.0-alpha.4",
80+
"@testing-library/react-12": "npm:@testing-library/react@^12",
8481
"@testing-library/react-hooks": "^3.4.2",
8582
"@testing-library/react-native": "^7.1.0",
8683
"@types/object-assign": "^4.0.30",
@@ -104,9 +101,12 @@
104101
"jest": "^26.6.1",
105102
"prettier": "^2.1.2",
106103
"react": "18.0.0-beta-fdc1d617a-20211118",
104+
"react-17": "npm:react@^17",
107105
"react-dom": "18.0.0-beta-fdc1d617a-20211118",
106+
"react-dom-17": "npm:react-dom@^17",
108107
"react-native": "^0.64.1",
109108
"react-test-renderer": "18.0.0-beta-fdc1d617a-20211118",
109+
"react-test-renderer-17": "npm:react-test-renderer@^17",
110110
"redux": "^4.0.5",
111111
"rimraf": "^3.0.2",
112112
"rollup": "^2.32.1",

src/components/Context.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface ReactReduxContextValue<
88
> {
99
store: Store<SS, A>
1010
subscription: Subscription
11+
getServerState?: () => SS
1112
}
1213

1314
export const ReactReduxContext =

src/components/Provider.tsx

+12-4
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,40 @@ import { createSubscription } from '../utils/Subscription'
44
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
55
import { Action, AnyAction, Store } from 'redux'
66

7-
export interface ProviderProps<A extends Action = AnyAction> {
7+
export interface ProviderProps<A extends Action = AnyAction, S = any> {
88
/**
99
* The single Redux store in your application.
1010
*/
11-
store: Store<any, A>
11+
store: Store<S, A>
12+
13+
/**
14+
* An optional server state snapshot. Will be used during initial hydration render if available, to ensure that the UI output is consistent with the HTML generated on the server.
15+
*/
16+
serverState?: S
17+
1218
/**
1319
* Optional context to be used internally in react-redux. Use React.createContext() to create a context to be used.
1420
* If this is used, you'll need to customize `connect` by supplying the same context provided to the Provider.
1521
* Initial value doesn't matter, as it is overwritten with the internal state of Provider.
1622
*/
17-
context?: Context<ReactReduxContextValue<any, A>>
23+
context?: Context<ReactReduxContextValue<S, A>>
1824
children: ReactNode
1925
}
2026

2127
function Provider<A extends Action = AnyAction>({
2228
store,
2329
context,
2430
children,
31+
serverState,
2532
}: ProviderProps<A>) {
2633
const contextValue = useMemo(() => {
2734
const subscription = createSubscription(store)
2835
return {
2936
store,
3037
subscription,
38+
getServerState: serverState ? () => serverState : undefined,
3139
}
32-
}, [store])
40+
}, [store, serverState])
3341

3442
const previousState = useMemo(() => store.getState(), [store])
3543

src/components/connect.tsx

+16-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ import defaultMapStateToPropsFactories from '../connect/mapStateToProps'
2727
import defaultMergePropsFactories from '../connect/mergeProps'
2828

2929
import { createSubscription, Subscription } from '../utils/Subscription'
30-
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
30+
import {
31+
useIsomorphicLayoutEffect,
32+
canUseDOM,
33+
} from '../utils/useIsomorphicLayoutEffect'
3134
import shallowEqual from '../utils/shallowEqual'
3235

3336
import {
@@ -124,6 +127,7 @@ function subscribeUpdates(
124127
return
125128
}
126129

130+
// TODO We're currently calling getState ourselves here, rather than letting `uSES` do it
127131
const latestStoreState = store.getState()
128132

129133
let newChildProps, error
@@ -157,6 +161,7 @@ function subscribeUpdates(
157161
childPropsFromStoreUpdate.current = newChildProps
158162
renderIsScheduled.current = true
159163

164+
// TODO This is hacky and not how `uSES` is meant to be used
160165
// Trigger the React `useSyncExternalStore` subscriber
161166
additionalSubscribeListener()
162167
}
@@ -597,6 +602,10 @@ function connect<
597602
? props.store!
598603
: contextValue!.store
599604

605+
const getServerState = didStoreComeFromContext
606+
? contextValue.getServerState
607+
: store.getState
608+
600609
const childPropsSelector = useMemo(() => {
601610
// The child props selector needs the store reference as an input.
602611
// Re-create this selector whenever the store changes.
@@ -724,10 +733,14 @@ function connect<
724733

725734
try {
726735
actualChildProps = useSyncExternalStore(
736+
// TODO We're passing through a big wrapper that does a bunch of extra side effects besides subscribing
727737
subscribeForReact,
738+
// TODO This is incredibly hacky. We've already processed the store update and calculated new child props,
739+
// TODO and we're just passing that through so it triggers a re-render for us rather than relying on `uSES`.
728740
actualChildPropsSelector,
729-
// TODO Need a real getServerSnapshot here
730-
actualChildPropsSelector
741+
getServerState
742+
? () => childPropsSelector(getServerState(), wrapperProps)
743+
: actualChildPropsSelector
731744
)
732745
} catch (err) {
733746
if (latestSubscriptionCallbackError.current) {

src/hooks/useSelector.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ export function createSelectorHook(
4848
}
4949
}
5050

51-
const { store } = useReduxContext()!
51+
const { store, getServerState } = useReduxContext()!
5252

5353
const selectedState = useSyncExternalStoreWithSelector(
5454
store.subscribe,
5555
store.getState,
5656
// TODO Need a server-side snapshot here
57-
store.getState,
57+
getServerState || store.getState,
5858
selector,
5959
equalityFn
6060
)

src/utils/useIsomorphicLayoutEffect.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import { useEffect, useLayoutEffect } from 'react'
99
// is created synchronously, otherwise a store update may occur before the
1010
// subscription is created and an inconsistent state may be observed
1111

12-
export const useIsomorphicLayoutEffect =
12+
// Matches logic in React's `shared/ExecutionEnvironment` file
13+
export const canUseDOM = !!(
1314
typeof window !== 'undefined' &&
1415
typeof window.document !== 'undefined' &&
1516
typeof window.document.createElement !== 'undefined'
16-
? useLayoutEffect
17-
: useEffect
17+
)
18+
19+
export const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect

yarn.lock

+16-9
Original file line numberDiff line numberDiff line change
@@ -8837,7 +8837,7 @@ __metadata:
88378837
languageName: node
88388838
linkType: hard
88398839

8840-
"react-is@npm:18.0.0-beta-fdc1d617a-20211118, react-is@npm:^18.0.0-beta-fdc1d617a-20211118":
8840+
"react-is@npm:18.0.0-beta-fdc1d617a-20211118":
88418841
version: 18.0.0-beta-fdc1d617a-20211118
88428842
resolution: "react-is@npm:18.0.0-beta-fdc1d617a-20211118"
88438843
checksum: 402e28c80019e9deaee919c4373606736ea36142d1d1e09e0cab89d52e8bd647f1176a383987cca5c4e83bfee33bba8091ab347363ea4bdd4f9c3a707812c8b9
@@ -8858,6 +8858,13 @@ __metadata:
88588858
languageName: node
88598859
linkType: hard
88608860

8861+
"react-is@npm:^18.0.0-rc.0":
8862+
version: 18.0.0-rc.0-next-f2a59df48-20211208
8863+
resolution: "react-is@npm:18.0.0-rc.0-next-f2a59df48-20211208"
8864+
checksum: e0f8b1c8ef759153072b7508f8832164651aa855c9c939d607090899d9d522c68ad12ec9630f34a56c46330542356207dfc09d744b8ffe2eccc45a83f296fcae
8865+
languageName: node
8866+
linkType: hard
8867+
88618868
"react-native-codegen@npm:^0.0.6":
88628869
version: 0.0.6
88638870
resolution: "react-native-codegen@npm:0.0.6"
@@ -8966,7 +8973,7 @@ __metadata:
89668973
react-17: "npm:react@^17"
89678974
react-dom: 18.0.0-beta-fdc1d617a-20211118
89688975
react-dom-17: "npm:react-dom@^17"
8969-
react-is: ^18.0.0-beta-fdc1d617a-20211118
8976+
react-is: ^18.0.0-rc.0
89708977
react-native: ^0.64.1
89718978
react-test-renderer: 18.0.0-beta-fdc1d617a-20211118
89728979
react-test-renderer-17: "npm:react-test-renderer@^17"
@@ -8976,9 +8983,9 @@ __metadata:
89768983
rollup-plugin-terser: ^7.0.2
89778984
ts-jest: 26.5.6
89788985
typescript: ^4.3.4
8979-
use-sync-external-store: 1.0.0-beta-fdc1d617a-20211118
8986+
use-sync-external-store: ^1.0.0-rc.0
89808987
peerDependencies:
8981-
react: ^18.0.0-beta
8988+
react: ^18.0.0-rc
89828989
peerDependenciesMeta:
89838990
react-dom:
89848991
optional: true
@@ -10845,12 +10852,12 @@ __metadata:
1084510852
languageName: node
1084610853
linkType: hard
1084710854

10848-
"use-sync-external-store@npm:1.0.0-beta-fdc1d617a-20211118":
10849-
version: 1.0.0-beta-fdc1d617a-20211118
10850-
resolution: "use-sync-external-store@npm:1.0.0-beta-fdc1d617a-20211118"
10855+
"use-sync-external-store@npm:^1.0.0-rc.0":
10856+
version: 1.0.0-rc.0-next-f2a59df48-20211208
10857+
resolution: "use-sync-external-store@npm:1.0.0-rc.0-next-f2a59df48-20211208"
1085110858
peerDependencies:
10852-
react: 18.0.0-beta-fdc1d617a-20211118
10853-
checksum: bb52d87a886731595163a3b6a07b671f9ea92ca6de35791f32ce186e2fea0f2989088f3dacfea3dff258e866e767ff1e87d18494e436523e67b17e59051a22e0
10859+
react: 18.0.0-rc.0-next-f2a59df48-20211208
10860+
checksum: 772bf9fe4b47f9cf21969cc839e21e11e2fbad63e4992c75402fabed7048f5afb559bee289c9a0c3b4bad7c1c5724f6cb63c5499ef7f249c395e86233102bccb
1085410861
languageName: node
1085510862
linkType: hard
1085610863

0 commit comments

Comments
 (0)