diff --git a/src/index.js b/src/index.js index e3fb93733..c2a7231dd 100644 --- a/src/index.js +++ b/src/index.js @@ -1,23 +1,11 @@ import React from 'react' -import { DndProvider } from 'react-dnd' -import { HTML5Backend } from 'react-dnd-html5-backend' import ReactDOM from 'react-dom' -import { Provider } from 'react-redux' import { rootDomId } from 'client/util' +import App from './router' import './client/websockets' -import { LayoutFlagsProvider } from 'contexts/LayoutFlagsContext' -import { clientRouter, history } from './router' -import createStore from './store' - -const store = createStore(history) +import './css/global/index.scss' ReactDOM.render( - - - - {clientRouter()} - - - , + , document.getElementById(rootDomId) ) diff --git a/src/router/index.js b/src/router/index.js index c637b1bf0..8d6aff4cd 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,38 +1,31 @@ import React from 'react' -import { StaticRouter, Switch, Route } from 'react-router' import { ConnectedRouter } from 'connected-react-router' -import { createBrowserHistory, createMemoryHistory } from 'history' -import HyloAppRouter from 'routes/HyloAppRouter' +import { Switch, Route } from 'react-router' +import { DndProvider } from 'react-dnd' +import { HTML5Backend } from 'react-dnd-html5-backend' +import { Provider } from 'react-redux' +import { LayoutFlagsProvider } from 'contexts/LayoutFlagsContext' +import createStore, { history } from '../store' import RootRouter from 'routes/RootRouter' -import '../css/global/index.scss' +import HyloAppRouter from 'routes/HyloAppRouter' -export const history = typeof window !== 'undefined' - ? createBrowserHistory() - : createMemoryHistory() +const store = createStore() -// Note: `/hyloApp/*` routes will not invoke auth as auth cookie is already passed from webview -export function clientRouter () { +export default function App () { require('client/rollbar') // set up handling of uncaught errors return ( - - - - - - - ) -} - -// Note: Server-side Rendering -// ref: https://github.com/Hylozoic/hylo-evo/issues/1069 -export function serverRouter (req, context) { - return ( - - - - - - + + + + + + + + + + + + ) } diff --git a/src/server/appMiddleware.js b/src/server/appMiddleware.js index 118f2fb2e..725599f97 100644 --- a/src/server/appMiddleware.js +++ b/src/server/appMiddleware.js @@ -1,33 +1,54 @@ import { getBrowserSnippet } from './newrelic' // this must be first import React from 'react' -import { once } from 'lodash' -import root from 'root-path' import { renderToString } from 'react-dom/server' -import { readFileSync } from 'fs' import { Provider } from 'react-redux' import { createMemoryHistory } from 'history' -import { serverRouter } from 'router' +import { StaticRouter } from 'react-router' +import root from 'root-path' +import { readFileSync } from 'fs' +import { once } from 'lodash' import createStore from '../store' +import RootRouter from 'routes/RootRouter' + +/* +Server-side Rendering Configuration + +Doesn't do much for us currently, ref: +https://github.com/Hylozoic/hylo-evo/issues/1069 + +* To remove simply eliminate the `renderToString` setup below, +and change the last line of this function to be: + +`return res.status(200).send(html(''))` + +* The NewRelic setup could be revisited. + +*/ export default function appMiddleware (req, res, next) { - // Note: add async data loading for more effective SSR - const history = createMemoryHistory() - const store = createStore(history) + // Note: Add async data loading here for more effective SSR + const store = createStore(createMemoryHistory()) const context = {} const markup = renderToString( - {serverRouter(req, context)} + + + ) - // context may now have been mutated; check its values and redirect, - // show an error, etc. as appropriate - // https://reacttraining.com/react-router/web/guides/server-rendering + /* + + Context may now have been mutated; check its values and redirect, + show an error, etc. as appropriate, ref: + https://v5.reactrouter.com/web/guides/server-rendering + + */ return res.status(200).send(html(markup)) } -// this is set up as a property to make it easy to mock in tests +// A property to make it easy to mock in tests appMiddleware.getIndexFile = once(() => { const indexPath = root('build/index.html') return readFileSync(indexPath, { encoding: 'utf-8' }) diff --git a/src/store/index.js b/src/store/index.js index 103c347d3..702213b3d 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,11 +1,22 @@ import { createStore } from 'redux' import createMiddleware from './middleware' -import rootReducer, { combinedReducers } from './reducers' +import { createBrowserHistory } from 'history' +import createRootReducer, { createCombinedReducers } from './reducers' + +// `window` check here is for SSR case (see `appMiddleware`) +// where a static memory history is passed +export const history = typeof window !== 'undefined' && createBrowserHistory() + +export function getEmptyState (providedHistory = history) { + const combinedReducers = createCombinedReducers(providedHistory) -export function getEmptyState () { return combinedReducers({}, { type: '' }) } -export default function (history, req) { - return createStore(rootReducer, getEmptyState(), createMiddleware(history, req)) +export default function configureStore (providedHistory = history, req) { + return createStore( + createRootReducer(providedHistory), + getEmptyState(providedHistory), + createMiddleware(providedHistory, req) + ) } diff --git a/src/store/middleware/optimisticMiddleware.js b/src/store/middleware/optimisticMiddleware.js index 431d719be..dabb8c169 100644 --- a/src/store/middleware/optimisticMiddleware.js +++ b/src/store/middleware/optimisticMiddleware.js @@ -4,7 +4,8 @@ import { SET_STATE } from 'store/constants' export default function optimisticMiddleware (store) { return next => action => { - let { payload, meta } = action + const { payload, meta } = action + if (get('optimistic', meta) && isPromise(payload)) { const prevState = store.getState() action.payload = action.payload.then( @@ -15,6 +16,7 @@ export default function optimisticMiddleware (store) { } ) } + return next(action) } } diff --git a/src/store/reducers/index.js b/src/store/reducers/index.js index fd7eb6dd1..d72815fbb 100644 --- a/src/store/reducers/index.js +++ b/src/store/reducers/index.js @@ -1,6 +1,5 @@ import { combineReducers } from 'redux' import { connectRouter } from 'connected-react-router' -import { history } from 'router' import orm from './ormReducer' import returnToPath from 'store/reducers/returnToPath' @@ -37,7 +36,7 @@ import SkillsToLearnSection from 'components/SkillsToLearnSection/SkillsToLearnS import TopicsSettings from 'routes/GroupSettings/TopicsSettingsTab/TopicsSettingsTab.store' import UserGroupsTab from 'routes/UserSettings/UserGroupsTab/UserGroupsTab.store' -export const combinedReducers = combineReducers({ +export const createCombinedReducers = history => combineReducers({ // Global store orm, router: connectRouter(history), @@ -74,8 +73,21 @@ export const combinedReducers = combineReducers({ UserGroupsTab }) -export default composeReducers( - combinedReducers, - resetStore, - handleSetState -) +export default function createRootReducer (history) { + return composeReducers( + createCombinedReducers(history), + /* + + DANGEROUS: These mutate and/or reset the entire state object + + Not sure why then need to added using our `composeReducers` + utility function with appears to do the same things as redux's + `combineReducers`. If I remember right correctly this is so these + somehow run in a 2nd reducer cycle to eliminate an infinite reducer + update condition? Not convinced they can't just go at the bottom above ^ + + */ + resetStore, + handleSetState + ) +} diff --git a/src/store/reducers/resetStore.js b/src/store/reducers/resetStore.js index b8b7bc8fd..6538a419f 100644 --- a/src/store/reducers/resetStore.js +++ b/src/store/reducers/resetStore.js @@ -8,7 +8,7 @@ export const KEYS_PRESERVED_ON_RESET = [ 'mixpanel' ] -export default function (state, action) { +export default function (state = null, action) { if (action.type === LOGOUT && !action.error) { return getEmptyState() } diff --git a/src/util/testing/reactTestingLibraryExtended.js b/src/util/testing/reactTestingLibraryExtended.js index 2c89cf842..9f7e41014 100644 --- a/src/util/testing/reactTestingLibraryExtended.js +++ b/src/util/testing/reactTestingLibraryExtended.js @@ -5,20 +5,19 @@ import { MemoryRouter } from 'react-router' import { Provider } from 'react-redux' import { createStore } from 'redux' import { render } from '@testing-library/react' -import { history } from 'router' -import rootReducer from 'store/reducers' +import createRootReducer from 'store/reducers' import createMiddleware from 'store/middleware' -import { getEmptyState } from 'store' +import { history, getEmptyState } from 'store' import { LayoutFlagsProvider } from 'contexts/LayoutFlagsContext' // Note: This is ran by default via `customRender` below, but it's necessary to manually // generate the store when pre-populating the ReduxORM in a test. Search across tests to // for examples. Merges `provideState` over default app empty state -export function generateStore (providedState, providedHistory) { +export function generateStore (providedState, providedHistory = history) { return createStore( - rootReducer, + createRootReducer(providedHistory), { ...getEmptyState(), ...providedState }, - createMiddleware(providedHistory || history) + createMiddleware(providedHistory) ) }