Skip to content

Commit

Permalink
Contain SSR setup, clean-up and add notes to our Redux store creation
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenjohnson committed Oct 21, 2022
1 parent e709c5c commit 62205c8
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 74 deletions.
18 changes: 3 additions & 15 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -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(
<LayoutFlagsProvider>
<DndProvider backend={HTML5Backend}>
<Provider store={store}>
{clientRouter()}
</Provider>
</DndProvider>
</LayoutFlagsProvider>,
<App />,
document.getElementById(rootDomId)
)
49 changes: 21 additions & 28 deletions src/router/index.js
Original file line number Diff line number Diff line change
@@ -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 (
<ConnectedRouter history={history}>
<Switch>
<Route path='/hyloApp' component={HyloAppRouter} />
<RootRouter />
</Switch>
</ConnectedRouter>
)
}

// Note: Server-side Rendering
// ref: https://github.com/Hylozoic/hylo-evo/issues/1069
export function serverRouter (req, context) {
return (
<StaticRouter location={req.url} context={context}>
<Switch>
<Route path='/hyloApp' component={HyloAppRouter} />
<RootRouter />
</Switch>
</StaticRouter>
<LayoutFlagsProvider>
<DndProvider backend={HTML5Backend}>
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route path='/hyloApp' component={HyloAppRouter} />
<RootRouter />
</Switch>
</ConnectedRouter>
</Provider>
</DndProvider>
</LayoutFlagsProvider>
)
}
45 changes: 33 additions & 12 deletions src/server/appMiddleware.js
Original file line number Diff line number Diff line change
@@ -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(
<Provider store={store}>
{serverRouter(req, context)}
<StaticRouter location={req.url} context={context}>
<RootRouter />
</StaticRouter>
</Provider>
)

// 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' })
Expand Down
19 changes: 15 additions & 4 deletions src/store/index.js
Original file line number Diff line number Diff line change
@@ -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)
)
}
4 changes: 3 additions & 1 deletion src/store/middleware/optimisticMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -15,6 +16,7 @@ export default function optimisticMiddleware (store) {
}
)
}

return next(action)
}
}
26 changes: 19 additions & 7 deletions src/store/reducers/index.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
)
}
2 changes: 1 addition & 1 deletion src/store/reducers/resetStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
11 changes: 5 additions & 6 deletions src/util/testing/reactTestingLibraryExtended.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
}

Expand Down

0 comments on commit 62205c8

Please sign in to comment.