-
-
Save markerikson/dc6cee36b5b6f8d718f2e24a249e0491 to your computer and use it in GitHub Desktop.
import React from "react"; | |
import ReactDOM from "react-dom"; | |
import configureStore from "./store/configureStore"; | |
const store = configureStore(); | |
const rootEl = document.getElementById("root"); | |
let render = () => { | |
const RootAppComponent = require("containers/RootAppComponent").default; | |
ReactDOM.render( | |
<RootAppComponent store={store} />, | |
rootEl | |
); | |
}; | |
if(module.hot) { | |
// Support hot reloading of components | |
// and display an overlay for runtime errors | |
const renderApp = render; | |
const renderError = (error) => { | |
const RedBox = require("redbox-react"); | |
ReactDOM.render( | |
<RedBox error={error} />, | |
rootEl, | |
); | |
}; | |
render = () => { | |
try { | |
renderApp(); | |
} | |
catch(error) { | |
renderError(error); | |
} | |
}; | |
module.hot.accept("./containers/Root", () => { | |
setTimeout(render); | |
}); | |
} | |
render(); |
import {createStore, applyMiddleware, compose} from "redux"; | |
import rootReducer from "../reducers/rootReducer"; | |
import thunk from "redux-thunk"; | |
import createSagaMiddleware from 'redux-saga'; | |
import SagaManager from "sagas/SagaManager"; | |
/** | |
* Based on the current environment variable, we need to make sure | |
* to exclude any DevTools-related code from the production builds. | |
* The code is envify'd - using 'DefinePlugin' in Webpack. | |
*/ | |
const sagaMiddleware = createSagaMiddleware(); | |
const middlewares = [thunk, sagaMiddleware]; | |
const storeEnhancers = []; | |
if(__DEV__) { | |
const DevTools = require("../containers/DevTools").default; | |
// If the user has the "Redux DevTools" browser extension installed, use that. | |
// Otherwise, hook up the in-page DevTools UI component. | |
const debugEnhancer = window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument(); | |
storeEnhancers.push(debugEnhancer); | |
} | |
const middlewareEnhancer = applyMiddleware(...middlewares); | |
storeEnhancers.unshift(middlewareEnhancer); | |
export default function configureStore(initialState) { | |
const store = createStore( | |
rootReducer, | |
initialState, | |
compose(...storeEnhancers) | |
); | |
// run sagas | |
SagaManager.startSagas(sagaMiddleware); | |
if(__DEV__) { | |
// Hot reload reducers (requires Webpack or Browserify HMR to be enabled) | |
if(module.hot) { | |
module.hot.accept("../reducers/rootReducer", () => | |
store.replaceReducer(require("../reducers/rootReducer").default) | |
); | |
module.hot.accept('../sagas/SagaManager', () => { | |
SagaManager.cancelSagas(store); | |
require('../sagas/SagaManager').default.startSagas(sagaMiddleware); | |
}); | |
} | |
} | |
return store; | |
} |
// Borrowed from https://gist.github.com/hoschi/6538249ad079116840825e20c48f1690 | |
// Note that reloading sagas has several issues/caveats to be aware of. | |
// See https://github.com/yelouafi/redux-saga/issues/22#issuecomment-218737951 for discussion. | |
import { take, fork, cancel } from 'redux-saga/effects'; | |
import rootSaga from "./rootSaga"; | |
const sagas = [rootSaga]; | |
export const CANCEL_SAGAS_HMR = 'CANCEL_SAGAS_HMR'; | |
function createAbortableSaga (saga) { | |
if (process.env.NODE_ENV === 'development') { | |
return function* main () { | |
const sagaTask = yield fork(saga); | |
yield take(CANCEL_SAGAS_HMR); | |
yield cancel(sagaTask); | |
}; | |
} else { | |
return saga; | |
} | |
} | |
const SagaManager = { | |
startSagas(sagaMiddleware) { | |
sagas.map(createAbortableSaga).forEach((saga) => sagaMiddleware.run(saga)); | |
}, | |
cancelSagas(store) { | |
store.dispatch({ | |
type: CANCEL_SAGAS_HMR | |
}); | |
} | |
}; | |
export default SagaManager; |
You are doing lords work
omg this is gold
tight
New to HMR sagas: Is the still the 2019/2020 way to go? Just askin' :-)
If you're using sagas, yes, this HMR approach should still work as far as I know.
That said, I would say that 95% of Redux apps don't actually need sagas:
And we would recommend starting with thunks by default:
https://redux.js.org/style-guide/style-guide#use-thunks-for-async-logic
Already using sagas to „listen“ to several actions (also dynamically picked by the user like in a hook system). So I don‘t doubt our decision there.
In one of the links rxjs is mentioned. I wonder how you would achieve a similiar behavior. Gotta dig a little deeper there.
Your links are awesome resources! Thanks a bunch!
When you say „we recommend“ who do you mean?
"We" as the Redux team (ie, mostly me, and also Tim Dorr).
this still works. thanks.
The only problem i noticed is that: if you change some file(configuration for example) that is used by both your component and saga, the app may hang. The reason: component gets rerendered first, triggered some action, which triggered some saga, but then that saga gets canceled/replaced, while the component may still wait for results from old sagas
=> I just noticed that you have something like this in your example:
module.hot.accept("./containers/Root", () => {
setTimeout(render);
});
That works.
Thanks.
Thanks for this! Helped me a lot.
I'd like to add it seems like it would be good practice to
ReactDOM.unmountComponentAtNode(rootEl)
beforeReactDOM.render