diff --git a/packages/create-instantsearch-app/__snapshots__/createInstantSearchApp.test.js.snap b/packages/create-instantsearch-app/__snapshots__/createInstantSearchApp.test.js.snap
index bc56d20ce..da837323a 100644
--- a/packages/create-instantsearch-app/__snapshots__/createInstantSearchApp.test.js.snap
+++ b/packages/create-instantsearch-app/__snapshots__/createInstantSearchApp.test.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Options with unknown template throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, InstantSearch.js, React InstantSearch, Vue InstantSearch"`;
+exports[`Options with unknown template throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, InstantSearch.js, React InstantSearch, React InstantSearch Native, Vue InstantSearch"`;
exports[`Options with unvalid name throws 1`] = `
"Could not create a project called \\"[31m./WrongNpmName[39m\\" because of npm naming restrictions.
@@ -8,8 +8,8 @@ exports[`Options with unvalid name throws 1`] = `
- name can only contain URL-friendly characters"
`;
-exports[`Options with wrong template path throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, InstantSearch.js, React InstantSearch, Vue InstantSearch"`;
+exports[`Options with wrong template path throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, InstantSearch.js, React InstantSearch, React InstantSearch Native, Vue InstantSearch"`;
exports[`Options without path throws 1`] = `"The option \`path\` is required."`;
-exports[`Options without template throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, InstantSearch.js, React InstantSearch, Vue InstantSearch"`;
+exports[`Options without template throws 1`] = `"The template directory must contain a configuration file \`.template.js\` or must be one of those: Angular InstantSearch, InstantSearch.js, React InstantSearch, React InstantSearch Native, Vue InstantSearch"`;
diff --git a/scripts/__snapshots__/e2e-templates.test.js.snap b/scripts/__snapshots__/e2e-templates.test.js.snap
index e16c6e300..a6d0b70ee 100644
--- a/scripts/__snapshots__/e2e-templates.test.js.snap
+++ b/scripts/__snapshots__/e2e-templates.test.js.snap
@@ -1610,6 +1610,320 @@ Array [
]
`;
+exports[`Templates React InstantSearch Native File content: .babelrc 1`] = `
+"{
+ \\"presets\\": [\\"babel-preset-expo\\"],
+ \\"env\\": {
+ \\"development\\": {
+ \\"plugins\\": [\\"transform-react-jsx-source\\"]
+ }
+ }
+}"
+`;
+
+exports[`Templates React InstantSearch Native File content: .editorconfig 1`] = `
+"root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true"
+`;
+
+exports[`Templates React InstantSearch Native File content: .eslintrc.js 1`] = `
+"module.exports = {
+ extends: 'algolia/react',
+};"
+`;
+
+exports[`Templates React InstantSearch Native File content: .gitignore 1`] = `
+"# See https://help.github.com/ignore-files/ for more about ignoring files.
+
+# expo
+.expo/
+
+# dependencies
+/node_modules
+
+# misc
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*"
+`;
+
+exports[`Templates React InstantSearch Native File content: .prettierrc 1`] = `
+"{
+ \\"singleQuote\\": true,
+ \\"proseWrap\\": \\"never\\",
+ \\"trailingComma\\": \\"es5\\"
+}"
+`;
+
+exports[`Templates React InstantSearch Native File content: .watchmanconfig 1`] = `"{}"`;
+
+exports[`Templates React InstantSearch Native File content: App.js 1`] = `
+"import React from 'react';
+import { StyleSheet, View, SafeAreaView, StatusBar } from 'react-native';
+import algoliasearch from 'algoliasearch/reactnative';
+import { InstantSearch } from 'react-instantsearch-native';
+import SearchBox from './src/SearchBox';
+import InfiniteHits from './src/InfiniteHits';
+
+const searchClient = algoliasearch(
+ 'appId',
+ 'apiKey'
+);
+
+const styles = StyleSheet.create({
+ safe: {
+ flex: 1,
+ backgroundColor: '#252b33',
+ },
+ container: {
+ flex: 1,
+ backgroundColor: '#FFFFFF',
+ },
+});
+
+class App extends React.Component {
+ root = {
+ Root: View,
+ props: {
+ style: {
+ flex: 1,
+ },
+ },
+ };
+
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default App;"
+`;
+
+exports[`Templates React InstantSearch Native File content: README.md 1`] = `
+"# react-instantsearch-native-app
+
+_This project was generated with [create-instantsearch-app](https://github.com/algolia/create-instantsearch-app) by [Algolia](https://algolia.com)._
+
+## Get started
+
+To run this project locally, install the dependencies and run the local server:
+
+\`\`\`sh
+npm install
+npm start
+\`\`\`
+
+Alternatively, you may use [Yarn](https://http://yarnpkg.com/):
+
+\`\`\`sh
+yarn
+yarn start
+\`\`\`"
+`;
+
+exports[`Templates React InstantSearch Native File content: app.json 1`] = `
+"{
+ \\"expo\\": {
+ \\"sdkVersion\\": \\"27.0.0\\"
+ }
+}"
+`;
+
+exports[`Templates React InstantSearch Native File content: package.json 1`] = `
+"{
+ \\"name\\": \\"react-instantsearch-native-app\\",
+ \\"version\\": \\"1.0.0\\",
+ \\"private\\": true,
+ \\"main\\": \\"./node_modules/react-native-scripts/build/bin/crna-entry.js\\",
+ \\"scripts\\": {
+ \\"start\\": \\"react-native-scripts start\\",
+ \\"android\\": \\"react-native-scripts android\\",
+ \\"ios\\": \\"react-native-scripts ios\\",
+ \\"lint\\": \\"eslint .\\",
+ \\"lint:fix\\": \\"npm run lint -- --fix\\"
+ },
+ \\"dependencies\\": {
+ \\"algoliasearch\\": \\"^3.27.1\\",
+ \\"expo\\": \\"^27.0.1\\",
+ \\"prop-types\\": \\"^15.6.1\\",
+ \\"react\\": \\"16.3.1\\",
+ \\"react-instantsearch-native\\": \\"^1.0.0\\",
+ \\"react-native\\": \\"^0.55.2\\"
+ },
+ \\"devDependencies\\": {
+ \\"eslint\\": \\"^4.19.1\\",
+ \\"eslint-config-algolia\\": \\"^13.1.0\\",
+ \\"eslint-config-prettier\\": \\"^2.9.0\\",
+ \\"eslint-plugin-import\\": \\"^2.12.0\\",
+ \\"eslint-plugin-prettier\\": \\"^2.6.0\\",
+ \\"eslint-plugin-react\\": \\"^7.9.1\\",
+ \\"prettier\\": \\"^1.13.4\\",
+ \\"react-native-scripts\\": \\"1.14.0\\"
+ }
+}"
+`;
+
+exports[`Templates React InstantSearch Native File content: src/Highlight.js 1`] = `
+"import React from 'react';
+import { Text } from 'react-native';
+import PropTypes from 'prop-types';
+import { connectHighlight } from 'react-instantsearch-native';
+
+const Highlight = ({ attribute, hit, highlight }) => {
+ const highlights = highlight({
+ highlightProperty: '_highlightResult',
+ attribute,
+ hit,
+ });
+
+ return (
+
+ {highlights.map(({ value, isHighlighted }, index) => {
+ const style = {
+ backgroundColor: isHighlighted ? 'yellow' : 'transparent',
+ };
+
+ return (
+
+ {value}
+
+ );
+ })}
+
+ );
+};
+
+Highlight.propTypes = {
+ attribute: PropTypes.string.isRequired,
+ hit: PropTypes.object.isRequired,
+ highlight: PropTypes.func.isRequired,
+};
+
+export default connectHighlight(Highlight);"
+`;
+
+exports[`Templates React InstantSearch Native File content: src/InfiniteHits.js 1`] = `
+"import React from 'react';
+import { StyleSheet, View, FlatList } from 'react-native';
+import PropTypes from 'prop-types';
+import { connectInfiniteHits } from 'react-instantsearch-native';
+import Highlight from './Highlight';
+
+const styles = StyleSheet.create({
+ separator: {
+ borderBottomWidth: 1,
+ },
+ item: {
+ padding: 10,
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+});
+
+const InfiniteHits = ({ hits, hasMore, refine }) => (
+ item.objectID}
+ ItemSeparatorComponent={() => }
+ onEndReached={() => hasMore && refine()}
+ renderItem={({ item }) => (
+
+
+
+ )}
+ />
+);
+
+InfiniteHits.propTypes = {
+ hits: PropTypes.arrayOf(PropTypes.object).isRequired,
+ hasMore: PropTypes.bool.isRequired,
+ refine: PropTypes.func.isRequired,
+};
+
+export default connectInfiniteHits(InfiniteHits);"
+`;
+
+exports[`Templates React InstantSearch Native File content: src/SearchBox.js 1`] = `
+"import React from 'react';
+import { StyleSheet, View, TextInput } from 'react-native';
+import PropTypes from 'prop-types';
+import { connectSearchBox } from 'react-instantsearch-native';
+
+const styles = StyleSheet.create({
+ container: {
+ padding: 10,
+ backgroundColor: '#252b33',
+ },
+ input: {
+ height: 40,
+ padding: 10,
+ backgroundColor: '#FFFFFF',
+ },
+});
+
+const SearchBox = ({ currentRefinement, refine }) => (
+
+ refine(value)}
+ value={currentRefinement}
+ placeholder=\\"Search placeholder\\"
+ />
+
+);
+
+SearchBox.propTypes = {
+ currentRefinement: PropTypes.string.isRequired,
+ refine: PropTypes.func.isRequired,
+};
+
+export default connectSearchBox(SearchBox);"
+`;
+
+exports[`Templates React InstantSearch Native Folder structure: contains the right files 1`] = `
+Array [
+ ".babelrc",
+ ".editorconfig",
+ ".eslintrc.js",
+ ".gitignore",
+ ".prettierrc",
+ ".watchmanconfig",
+ "App.js",
+ "README.md",
+ "app.json",
+ "package.json",
+ "src/Highlight.js",
+ "src/InfiniteHits.js",
+ "src/SearchBox.js",
+]
+`;
+
exports[`Templates Vue InstantSearch File content: .babelrc 1`] = `
"{
\\"presets\\": [
diff --git a/templates/React InstantSearch Native/.babelrc.template b/templates/React InstantSearch Native/.babelrc.template
new file mode 100644
index 000000000..2bcd546db
--- /dev/null
+++ b/templates/React InstantSearch Native/.babelrc.template
@@ -0,0 +1,8 @@
+{
+ "presets": ["babel-preset-expo"],
+ "env": {
+ "development": {
+ "plugins": ["transform-react-jsx-source"]
+ }
+ }
+}
diff --git a/templates/React InstantSearch Native/.editorconfig b/templates/React InstantSearch Native/.editorconfig
new file mode 100644
index 000000000..9d08a1a82
--- /dev/null
+++ b/templates/React InstantSearch Native/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/templates/React InstantSearch Native/.eslintrc.js b/templates/React InstantSearch Native/.eslintrc.js
new file mode 100644
index 000000000..5b815c0d4
--- /dev/null
+++ b/templates/React InstantSearch Native/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: 'algolia/react',
+};
diff --git a/templates/React InstantSearch Native/.gitignore.template b/templates/React InstantSearch Native/.gitignore.template
new file mode 100644
index 000000000..14a11718a
--- /dev/null
+++ b/templates/React InstantSearch Native/.gitignore.template
@@ -0,0 +1,17 @@
+# See https://help.github.com/ignore-files/ for more about ignoring files.
+
+# expo
+.expo/
+
+# dependencies
+/node_modules
+
+# misc
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/templates/React InstantSearch Native/.prettierrc b/templates/React InstantSearch Native/.prettierrc
new file mode 100644
index 000000000..833f03b62
--- /dev/null
+++ b/templates/React InstantSearch Native/.prettierrc
@@ -0,0 +1,5 @@
+{
+ "singleQuote": true,
+ "proseWrap": "never",
+ "trailingComma": "es5"
+}
diff --git a/templates/React InstantSearch Native/.template.js b/templates/React InstantSearch Native/.template.js
new file mode 100644
index 000000000..c0decc17e
--- /dev/null
+++ b/templates/React InstantSearch Native/.template.js
@@ -0,0 +1,13 @@
+const install = require('../../packages/tasks/node/install');
+const teardown = require('../../packages/tasks/node/teardown');
+
+module.exports = {
+ libraryName: 'react-instantsearch-native',
+ templateName: 'react-instantsearch-native',
+ appName: 'react-instantsearch-native-app',
+ keywords: ['algolia', 'instantSearch', 'react', 'react-native', 'react-instantsearch-native'],
+ tasks: {
+ install,
+ teardown,
+ },
+};
diff --git a/templates/React InstantSearch Native/.watchmanconfig b/templates/React InstantSearch Native/.watchmanconfig
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/templates/React InstantSearch Native/.watchmanconfig
@@ -0,0 +1 @@
+{}
diff --git a/templates/React InstantSearch Native/App.js.hbs b/templates/React InstantSearch Native/App.js.hbs
new file mode 100644
index 000000000..01da44f9f
--- /dev/null
+++ b/templates/React InstantSearch Native/App.js.hbs
@@ -0,0 +1,53 @@
+import React from 'react';
+import { StyleSheet, View, SafeAreaView, StatusBar } from 'react-native';
+import algoliasearch from 'algoliasearch/reactnative';
+import { InstantSearch } from 'react-instantsearch-native';
+import SearchBox from './src/SearchBox';
+import InfiniteHits from './src/InfiniteHits';
+
+const searchClient = algoliasearch(
+ '{{appId}}',
+ '{{apiKey}}'
+);
+
+const styles = StyleSheet.create({
+ safe: {
+ flex: 1,
+ backgroundColor: '#252b33',
+ },
+ container: {
+ flex: 1,
+ backgroundColor: '#FFFFFF',
+ },
+});
+
+class App extends React.Component {
+ root = {
+ Root: View,
+ props: {
+ style: {
+ flex: 1,
+ },
+ },
+ };
+
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default App;
diff --git a/templates/React InstantSearch Native/README.md b/templates/React InstantSearch Native/README.md
new file mode 100644
index 000000000..d50acaa49
--- /dev/null
+++ b/templates/React InstantSearch Native/README.md
@@ -0,0 +1,19 @@
+# {{name}}
+
+_This project was generated with [create-instantsearch-app](https://github.com/algolia/create-instantsearch-app) by [Algolia](https://algolia.com)._
+
+## Get started
+
+To run this project locally, install the dependencies and run the local server:
+
+```sh
+npm install
+npm start
+```
+
+Alternatively, you may use [Yarn](https://http://yarnpkg.com/):
+
+```sh
+yarn
+yarn start
+```
diff --git a/templates/React InstantSearch Native/app.json b/templates/React InstantSearch Native/app.json
new file mode 100644
index 000000000..752f6170e
--- /dev/null
+++ b/templates/React InstantSearch Native/app.json
@@ -0,0 +1,5 @@
+{
+ "expo": {
+ "sdkVersion": "27.0.0"
+ }
+}
diff --git a/templates/React InstantSearch Native/package.json b/templates/React InstantSearch Native/package.json
new file mode 100644
index 000000000..8deef9361
--- /dev/null
+++ b/templates/React InstantSearch Native/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "{{name}}",
+ "version": "1.0.0",
+ "private": true,
+ "main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
+ "scripts": {
+ "start": "react-native-scripts start",
+ "android": "react-native-scripts android",
+ "ios": "react-native-scripts ios",
+ "lint": "eslint .",
+ "lint:fix": "npm run lint -- --fix"
+ },
+ "dependencies": {
+ "algoliasearch": "^3.27.1",
+ "expo": "^27.0.1",
+ "prop-types": "^15.6.1",
+ "react": "16.3.1",
+ "react-instantsearch-native": "^{{libraryVersion}}",
+ "react-native": "^0.55.2"
+ },
+ "devDependencies": {
+ "eslint": "^4.19.1",
+ "eslint-config-algolia": "^13.1.0",
+ "eslint-config-prettier": "^2.9.0",
+ "eslint-plugin-import": "^2.12.0",
+ "eslint-plugin-prettier": "^2.6.0",
+ "eslint-plugin-react": "^7.9.1",
+ "prettier": "^1.13.4",
+ "react-native-scripts": "1.14.0"
+ }
+}
diff --git a/templates/React InstantSearch Native/src/Highlight.js b/templates/React InstantSearch Native/src/Highlight.js
new file mode 100644
index 000000000..aefc156ff
--- /dev/null
+++ b/templates/React InstantSearch Native/src/Highlight.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import { Text } from 'react-native';
+import PropTypes from 'prop-types';
+import { connectHighlight } from 'react-instantsearch-native';
+
+const Highlight = ({ attribute, hit, highlight }) => {
+ const highlights = highlight({
+ highlightProperty: '_highlightResult',
+ attribute,
+ hit,
+ });
+
+ return (
+
+ {highlights.map(({ value, isHighlighted }, index) => {
+ const style = {
+ backgroundColor: isHighlighted ? 'yellow' : 'transparent',
+ };
+
+ return (
+
+ {value}
+
+ );
+ })}
+
+ );
+};
+
+Highlight.propTypes = {
+ attribute: PropTypes.string.isRequired,
+ hit: PropTypes.object.isRequired,
+ highlight: PropTypes.func.isRequired,
+};
+
+export default connectHighlight(Highlight);
diff --git a/templates/React InstantSearch Native/src/InfiniteHits.js.hbs b/templates/React InstantSearch Native/src/InfiniteHits.js.hbs
new file mode 100644
index 000000000..5cd7ea1c1
--- /dev/null
+++ b/templates/React InstantSearch Native/src/InfiniteHits.js.hbs
@@ -0,0 +1,44 @@
+import React from 'react';
+import { StyleSheet, {{#unless mainAttribute}}Text, {{/unless}}View, FlatList } from 'react-native';
+import PropTypes from 'prop-types';
+import { connectInfiniteHits } from 'react-instantsearch-native';
+{{#if mainAttribute}}
+import Highlight from './Highlight';
+{{/if}}
+
+const styles = StyleSheet.create({
+ separator: {
+ borderBottomWidth: 1,
+ },
+ item: {
+ padding: 10,
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+});
+
+const InfiniteHits = ({ hits, hasMore, refine }) => (
+ item.objectID}
+ ItemSeparatorComponent={() => }
+ onEndReached={() => hasMore && refine()}
+ renderItem={({ item }) => (
+
+ {{#if mainAttribute}}
+
+ {{else}}
+ {JSON.stringify(item).slice(0, 100)}
+ {{/if}}
+
+ )}
+ />
+);
+
+InfiniteHits.propTypes = {
+ hits: PropTypes.arrayOf(PropTypes.object).isRequired,
+ hasMore: PropTypes.bool.isRequired,
+ refine: PropTypes.func.isRequired,
+};
+
+export default connectInfiniteHits(InfiniteHits);
diff --git a/templates/React InstantSearch Native/src/SearchBox.js.hbs b/templates/React InstantSearch Native/src/SearchBox.js.hbs
new file mode 100644
index 000000000..12ad96c83
--- /dev/null
+++ b/templates/React InstantSearch Native/src/SearchBox.js.hbs
@@ -0,0 +1,34 @@
+import React from 'react';
+import { StyleSheet, View, TextInput } from 'react-native';
+import PropTypes from 'prop-types';
+import { connectSearchBox } from 'react-instantsearch-native';
+
+const styles = StyleSheet.create({
+ container: {
+ padding: 10,
+ backgroundColor: '#252b33',
+ },
+ input: {
+ height: 40,
+ padding: 10,
+ backgroundColor: '#FFFFFF',
+ },
+});
+
+const SearchBox = ({ currentRefinement, refine }) => (
+
+ refine(value)}
+ value={currentRefinement}
+ placeholder="{{searchPlaceholder}}"
+ />
+
+);
+
+SearchBox.propTypes = {
+ currentRefinement: PropTypes.string.isRequired,
+ refine: PropTypes.func.isRequired,
+};
+
+export default connectSearchBox(SearchBox);