jcicero518
3/20/2018 - 4:52 PM

Webpack React/Redux Hot Module Reloading (HMR) example

Webpack React/Redux Hot Module Reloading (HMR) example

// 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;
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;
}
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();