Jump to: Automatic setup | Manual setup
For smaller project, we provide automatic option for setting up Hot Module Replacement. If your project meets the following criteria, you can use our on-line setup for HMR:
- Single root component exported from the root file with
default
keyword (export default
) - Single
AppRegistry.registerComponent
call - Root component and
AppRegistry.registerComponent
call are in the same file
So, the root file (index.ios.js
or index.android.js
) should look more or less like this:
import React, { Component } from 'react';
import {
AppRegistry,
/* ... */
} from 'react-native';
export default class MyProject extends Component {
/* ... */
}
/* ... */
AppRegistry.registerComponent('MyProject', () => MyProject);
All default index.ios.js
and index.android.js
files created by react-native init
work with the automatic setup out of the box.
In order to enable HMR add import 'haul/hot';
at the top of the root file - usually index.ios.js
or index.android.js
. Then, from within Developer menu tap Enable Hot Reloading.
Navigate to: API docs
Jump to: Examples
For advanced projects or the ones with different structure, we provide our own HMR API, which allows you to enable HMR regardless of which navigation library you use and how you structure your code.
This guide below is not a list of steps, but rather a set of rules and tips on how to setup HMR, since we don't know how your project looks like. However, if you're using one of the following naviagion libraries, please refer to associated guide:
Haul uses react-hot-loader
to support HMR, so the first step is to patch React's createElement
and createFactory
functions to use react-proxy
. Proxied component can be updated with a new implementation but it's state is presisted between updates.
In order to do that, add the following line to the root file - the one that is used as an entrypoint, before any other code:
import 'haul/hot/patch';
It's important that the code from haul/hot/patch
is evaluated before anything else!
Now, we need to wrap the root component factories using makeHot
function. Root component factory is a function that returns a root component:
() => RootComponent
You can encounter those in AppRegistry.registerComponent
or Navigation.registerComponent
calls:
AppRegistry.registerComponent('MyApp', () => MyApp);
// or
Navigation.registerComponent('MyScreen', () => MyScreen);
After wrapping those with makeHot
it should look like this:
AppRegistry.registerComponent('MyApp', makeHot(() => MyApp));
// or
Navigation.registerComponent('MyScreen', makeHot(() => MyScreen));
If your app has multiple root components (usually they're treated as screens), you need to pass the second argument to makeHot
call, which is a id
of the component factory. It will be used later to tell which component needs to be redrawn:
Navigation.registerComponent('MyScreen1', makeHot(() => MyScreen1, 'MyScreen1'));
Navigation.registerComponent('MyScreen2', makeHot(() => MyScreen2, 'MyScreen2'));
Navigation.registerComponent('MyScreen3', makeHot(() => MyScreen3, 'MyScreen3'));
All of the functions we use in this guide can be imported from haul/hot
:
import {
makeHot,
tryUpdateSelf,
callOnce,
clearCacheFor,
redraw
} from 'haul/hot';
Next step is optional and can be applied only if you have a single root component and registerComponent
call in same file. In that case, when the file will be re-evaluated upon hot update, the app won't be refreshed unless you add
tryUpdateSelf();
somewhere in the file. This function will try to rerender the root component.
There's also one thing to keep in mind, if you have the root component and registerComponent
call in single file, you should use callOnce
function and pass a callback with code that should run only once, not on every re-evaluation.
callOnce(() => {
AppRegistry.registerComponent('MyApp', makeHot(() => MyApp));
/* ... other logic, which should run only once ... */
});
The last thing is to acually tell the Webpack which modules we want to accept and how to update them. You can safely copy-pase the following snippet:
if (module.hot) {
module.hot.accept(() => {})
module.hot.accept('', () => {
clearCacheFor(require.resolve(''));
redraw(() => require('').default);
});
}
Now, you need to replace empty strings with the paths according to the following rules:
- If you have a single root component,
module.hot.accept
first argument should be an array of paths to children components and the rest of the paths should be to itself:// file: index.js import Child1 from './Child1'; import Child2 from './Child2'; /* ... */ export default MyApp { /* ... */ } /* ... */ if (module.hot) { module.hot.accept(() => {}) module.hot.accept(['./Child1', './Child2'], () => { clearCacheFor(require.resolve('./index')); redraw(() => require('./index').default); }); }
- If you have a multi root component, all of the paths should point to the root component and each of the root components should have a separate
module.hot.accept
call:// file: index.js import Root1 from './Root1'; import Root2 from './Root2'; /* ... */ if (module.hot) { module.hot.accept(() => {}) module.hot.accept('./Root1', () => { clearCacheFor(require.resolve('./Root1')); redraw(() => require('./Root1').default, 'Root1'); }); module.hot.accept('./Root2', () => { clearCacheFor(require.resolve('./Root2')); redraw(() => require('./Root2').default, 'Root2'); }); }
import 'haul/hot/patch';
import {
makeHot,
tryUpdateSelf,
callOnce,
clearCacheFor,
redraw
} from 'haul/hot';
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
} from 'react-native';
import Header from './Header';
export default class MyApp extends Component {
render() {
return (
<View style={styles.container}>
<Header />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});
trySelfUpdate();
callOnce(() => {
AppRegistry.registerComponent('MyApp', makeHot(() => MyApp));
});
if (module.hot) {
module.hot.accept(() => {})
module.hot.accept(['./Header'], () => {
clearCacheFor(require.resolve('./index'));
redraw(() => require('./index').default);
});
}
// index.js
import 'haul/hot/patch';
import './screens';
// screens.js
import {
makeHot,
tryUpdateSelf,
callOnce,
clearCacheFor,
redraw
} from 'haul/hot';
import Navigation from 'some-naviagtion-lib';
import Calendar from './Calendar';
import Location from './Location';
Navigation.registerComponent('Calendar', makeHot(() => Calendar, 'Calendar'));
Navigation.registerComponent('Location', makeHot(() => Location, 'Location'));
if (module.hot) {
module.hot.accept('./Calendar', () => {
clearCacheFor(require.resolve('./Calendar'));
redraw(() => require('./Calendar').default);
});
module.hot.accept('./Location', () => {
clearCacheFor(require.resolve('./Location'));
redraw(() => require('./Location').default);
});
}