Last active
October 30, 2024 10:11
-
-
Save markerikson/dc6cee36b5b6f8d718f2e24a249e0491 to your computer and use it in GitHub Desktop.
Webpack React/Redux Hot Module Reloading (HMR) example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; |
"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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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?