diff --git a/keyserver/src/responders/website-responders.js b/keyserver/src/responders/website-responders.js --- a/keyserver/src/responders/website-responders.js +++ b/keyserver/src/responders/website-responders.js @@ -237,6 +237,7 @@ commServicesAccessToken: t.Nil, inviteLinksStore: inviteLinksStoreValidator, keyserverStore: keyserverStoreValidator, + initialStateLoaded: tBool(false), }); async function websiteResponder( @@ -603,6 +604,7 @@ commServicesAccessToken: null, inviteLinksStore: inviteLinksStorePromise, keyserverStore: keyserverStorePromise, + initialStateLoaded: false, }); const validatedInitialReduxState = validateOutput( viewer.platformDetails, diff --git a/web/redux/initial-state-gate.js b/web/redux/initial-state-gate.js new file mode 100644 --- /dev/null +++ b/web/redux/initial-state-gate.js @@ -0,0 +1,68 @@ +// @flow + +import * as React from 'react'; +import { useDispatch } from 'react-redux'; +import { PersistGate } from 'redux-persist/es/integration/react.js'; +import type { Persistor } from 'redux-persist/es/types'; + +import { useServerCall } from 'lib/utils/action-utils.js'; +import { infoFromURL } from 'lib/utils/url-utils.js'; + +import { getInitialReduxState, setInitialReduxState } from './action-types.js'; +import { useSelector } from './redux-utils.js'; +import Loading from '../loading.react.js'; + +type Props = { + +persistor: Persistor, + +children: React.Node, +}; +function InitialReduxStateGate(props: Props): React.Node { + const { children, persistor } = props; + const callGetInitialReduxState = useServerCall(getInitialReduxState); + const dispatch = useDispatch(); + + const [initError, setInitError] = React.useState(null); + React.useEffect(() => { + if (initError) { + throw initError; + } + }); + + const isRehydrated = useSelector( + state => state._persist?.rehydrated ?? false, + ); + const prevIsRehydrated = React.useRef(false); + React.useEffect(() => { + if (!prevIsRehydrated.current && isRehydrated) { + prevIsRehydrated.current = isRehydrated; + (async () => { + try { + const urlInfo = infoFromURL(decodeURI(window.location.href)); + const payload = await callGetInitialReduxState(urlInfo); + dispatch({ type: setInitialReduxState, payload }); + } catch (err) { + setInitError(err); + } + })(); + } + }, [callGetInitialReduxState, dispatch, isRehydrated]); + + const initialStateLoaded = useSelector(state => state.initialStateLoaded); + + const childFunction = React.useCallback( + // This argument is passed from `PersistGate`. It means that the state is + // rehydrated and we can start fetching the initial info. + bootstrapped => { + if (bootstrapped && initialStateLoaded) { + return children; + } else { + return ; + } + }, + [children, initialStateLoaded], + ); + + return {childFunction}; +} + +export default InitialReduxStateGate; diff --git a/web/redux/redux-setup.js b/web/redux/redux-setup.js --- a/web/redux/redux-setup.js +++ b/web/redux/redux-setup.js @@ -97,6 +97,7 @@ +commServicesAccessToken: ?string, +inviteLinksStore: InviteLinksStore, +keyserverStore: KeyserverStore, + +initialStateLoaded: boolean, }; export type Action = @@ -140,6 +141,7 @@ }, }, }, + initialStateLoaded: true, }); } else if (action.type === updateWindowDimensionsActionType) { return validateState(oldState, { diff --git a/web/root.js b/web/root.js --- a/web/root.js +++ b/web/root.js @@ -7,7 +7,6 @@ import { createStore, applyMiddleware, type Store } from 'redux'; import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction.js'; import { persistReducer, persistStore } from 'redux-persist'; -import { PersistGate } from 'redux-persist/es/integration/react.js'; import thunk from 'redux-thunk'; import { reduxLoggerMiddleware } from 'lib/utils/action-logger.js'; @@ -16,7 +15,7 @@ import { SQLiteDataHandler } from './database/sqlite-data-handler.js'; import { localforageConfig } from './database/utils/constants.js'; import ErrorBoundary from './error-boundary.react.js'; -import Loading from './loading.react.js'; +import InitialReduxStateGate from './redux/initial-state-gate.js'; import { persistConfig } from './redux/persist.js'; import { type AppState, type Action, reducer } from './redux/redux-setup.js'; import history from './router-history.js'; @@ -36,15 +35,15 @@ const RootProvider = (): React.Node => ( - }> - + + - - + + );