├── .gitignore ├── App.fs ├── App.fsproj ├── README.md ├── index.js ├── minimum └── README.md ├── navigation ├── App.fs ├── App.fsproj ├── Counter.fs └── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .ionide/ 2 | .fable/ 3 | node_modules/ 4 | bin/ 5 | obj/ -------------------------------------------------------------------------------- /App.fs: -------------------------------------------------------------------------------- 1 | module App 2 | 3 | open Elmish 4 | open Elmish.React 5 | open Elmish.ReactNative 6 | open Fable.ReactNative 7 | 8 | type Model = { 9 | Counter : int 10 | } 11 | 12 | type Message = 13 | | Increment 14 | 15 | let init () = {Counter = 0}, Cmd.none 16 | 17 | let update msg model = 18 | match msg with 19 | | Increment -> 20 | {model with Counter = model.Counter + 1}, Cmd.none 21 | 22 | module R = Fable.ReactNative.Helpers 23 | module P = Fable.ReactNative.Props 24 | open Fable.ReactNative.Props 25 | 26 | let view model dispatch = 27 | 28 | R.view [ 29 | P.ViewProperties.Style [ 30 | P.FlexStyle.Flex 1.0 31 | P.FlexStyle.JustifyContent JustifyContent.Center 32 | P.BackgroundColor "#131313" ] 33 | ] [ 34 | 35 | 36 | R.text [ 37 | P.TextProperties.Style [ P.Color "#ffffff" ] 38 | ] "Press me" 39 | |> R.touchableHighlightWithChild [ 40 | P.TouchableHighlightProperties.Style [ 41 | P.FlexStyle.Padding (R.dip 10.) 42 | ] 43 | P.TouchableHighlightProperties.UnderlayColor "#f6f6f6" 44 | OnPress (fun _ -> dispatch Increment) 45 | ] 46 | 47 | R.text [ 48 | P.TextProperties.Style [ 49 | P.Color "#ffffff" 50 | P.FontSize 30. 51 | P.TextAlign P.TextAlignment.Center 52 | ] 53 | ] (string model.Counter) 54 | ] 55 | 56 | Program.mkProgram init update view 57 | |> Program.withConsoleTrace 58 | |> Program.withReactNative "Your project name" 59 | |> Program.run -------------------------------------------------------------------------------- /App.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How-to: Set up React Native with F# and Fable 2 | 3 | This is a step-by-step guide which aims to help you set up a React Native project with F# and Fable. The [Fable compiler](https://github.com/fable-compiler/Fable) generates JavaScript from your F# source code, and enables you to write React Native apps targeting iOS and Android almost completely through F#! Since the Fable compiler is just generating pure JavaScript from our F# source code we should be able to target any platform through React Native (Windows, MacOS, Web), though i have not tested this myself. Feel free to share your own experience with any of these platforms. 4 | 5 | Sample files for getting started can be found within this repository. 6 | 7 | ## Requirements 8 | - React Native 9 | - Watchman 10 | - Node.js 11 | - .NET Core >= 3.0 12 | - npm 13 | 14 | # Setup a new React Native project 15 | 16 | This how-to will not go into detail on how to install React Native and how React Native works. When you have React Native installed you can create a new project by running `react-native init ` 17 | 18 | You should test that your basic React Native project compiles/runs before moving further. 19 | 20 | `npx react-native run-ios` or `npx react-native run-android` 21 | 22 | # Setup the F# project 23 | Create a folder to hold your F# project and add a `.fsproj` file with a simple `.fs` file. In this example i will create a `src` folder in the project root directory which contains the files `App.fsproj` and `App.fs`. 24 | 25 | ## `App.fsproj` 26 | ```xml 27 | 28 | 29 | 30 | netstandard2.1 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ``` 44 | 45 | ## `App.fs` 46 | ```fsharp 47 | module App 48 | 49 | open Elmish 50 | open Elmish.React 51 | open Elmish.ReactNative 52 | open Fable.ReactNative 53 | 54 | // A very simple app which increments a number when you press a button 55 | 56 | type Model = { 57 | Counter : int 58 | } 59 | 60 | type Message = 61 | | Increment 62 | 63 | let init () = {Counter = 0}, Cmd.none 64 | 65 | let update msg model = 66 | match msg with 67 | | Increment -> 68 | {model with Counter = model.Counter + 1}, Cmd.none 69 | 70 | module R = Fable.ReactNative.Helpers 71 | module P = Fable.ReactNative.Props 72 | open Fable.ReactNative.Props 73 | 74 | let view model dispatch = 75 | R.view [ 76 | P.ViewProperties.Style [ 77 | P.FlexStyle.Flex 1.0 78 | P.FlexStyle.JustifyContent JustifyContent.Center 79 | P.BackgroundColor "#131313" ] 80 | ] [ 81 | 82 | 83 | R.text [ 84 | P.TextProperties.Style [ P.Color "#ffffff" ] 85 | ] "Press me" 86 | |> R.touchableHighlightWithChild [ 87 | P.TouchableHighlightProperties.Style [ 88 | P.FlexStyle.Padding (R.dip 10.) 89 | ] 90 | P.TouchableHighlightProperties.UnderlayColor "#f6f6f6" 91 | OnPress (fun _ -> dispatch Increment) 92 | ] 93 | 94 | R.text [ 95 | P.TextProperties.Style [ 96 | P.Color "#ffffff" 97 | P.FontSize 30. 98 | P.TextAlign P.TextAlignment.Center 99 | ] 100 | ] (string model.Counter) 101 | ] 102 | 103 | Program.mkProgram init update view 104 | |> Program.withConsoleTrace 105 | |> Program.withReactNative "Your project name" // CHANGE ME 106 | |> Program.run 107 | ``` 108 | 109 | IMPORTANT: Feed the name of your project in `Program.withReactNative` (the same you used for `react-native init` ) 110 | 111 | # Install Fable tool 112 | 113 | `dotnet new tool-manifest && dotnet tool install fable` 114 | 115 | # Install npm-packages 116 | 117 | Install the [@babel/preset-env](https://www.npmjs.com/package/@babel/preset-env) npm-module as a dev-dependency. See the documentation of these for further info. 118 | 119 | `npm install --save-dev @babel/preset-env` 120 | 121 | You will also need to install the [buffer](https://www.npmjs.com/package/buffer) npm-module, along with the [@react-native-community/netinfo](https://www.npmjs.com/package/@react-native-community/netinfo) module which is required by Fable.React.Native. 122 | 123 | `npm install buffer @react-native-community/netinfo` 124 | 125 | You can now compile your F# project to Javascript by simply running `dotnet fable ./src -o ./out` 126 | (Note the `-o` parameter specifying the output folder to dump the .js files) 127 | 128 | If you get a compilation error it is likely to be caused by your `babel.config.js` file, and i've experienced that the easiest way to get rid of this i simply by deleting the `babel.config.js` file altogether. You can also provide a configuration file as shown below. However, someone with a better Babel understanding than me could probably provide a better configuration/setup (suggestions welcomed). 129 | 130 | #### Tips: 131 | ```json 132 | "build": "dotnet fable ./src -o ./out", 133 | "watch": "dotnet fable watch ./src -o ./out" 134 | ``` 135 | Add the above JSON to the `scripts` section of the `packages.json` file and simply call `npm run build` to compile. Run `npm run watch` in order to watch for changes and enable hot-reloading as you change your F# code. 136 | 137 | # Importing the generated JavaScript 138 | Now you can compile your F# code to JavaScript and dump it to a folder (`./out` used in this example). 139 | 140 | 1. Delete your default `App.js` file in the root directory. 141 | 2. Update your `index.js` file: 142 | ```js 143 | /** 144 | * @format 145 | */ 146 | 147 | import { AppRegistry } from 'react-native'; 148 | import * as App from './out/App'; 149 | import { name as appName } from './app.json'; 150 | ``` 151 | Notice that we import App from our generated files in the `out` folder. The app registration call is also removed, as this is now handled in our F# code. 152 | 153 | # You're good to go! 154 | 1. Compile F# to JavaScript and watch for changes 155 | - `dotnet fable watch ./src -o ./out` 156 | - or `npm run watch` if you altered the `scripts` section of `packages.json` 157 | 2. Run app 158 | - `npx react-native run-ios|android` 159 | 3. Watch as the app updates along with your F# code. Enjoy! 160 | 161 | # More 162 | - For larger apps you might want to opt out of Elmish and include navigation. Take a look at the following [how-to](navigation) -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import { AppRegistry } from 'react-native'; 6 | import * as App from './out/App'; 7 | import { name as appName } from './app.json'; -------------------------------------------------------------------------------- /minimum/README.md: -------------------------------------------------------------------------------- 1 | # React Native with Fable from scratch 2 | 3 | -------------------------------------------------------------------------------- /navigation/App.fs: -------------------------------------------------------------------------------- 1 | module App 2 | 3 | open Fable.ReactNative.Navigation 4 | 5 | module R = Fable.ReactNative.Helpers 6 | module P = Fable.ReactNative.Props 7 | open Fable.ReactNative.Props 8 | 9 | let homePage (nav : Types.INavigation<_>) = 10 | R.view [ 11 | P.ViewProperties.Style [ 12 | P.FlexStyle.Flex 1. 13 | P.FlexStyle.JustifyContent JustifyContent.Center 14 | P.FlexStyle.AlignItems ItemAlignment.Center 15 | ] 16 | ] [ 17 | R.text [] "This is the home screen" 18 | 19 | R.touchableOpacity [ 20 | OnPress(fun _ -> 21 | // push a new instance of the counter screen to the stack 22 | nav.navigation.push "counter" 23 | ) 24 | ] [ 25 | R.text [ 26 | P.TextProperties.Style [ 27 | P.FlexStyle.MarginTop (R.pct 5.) 28 | ] 29 | ] "Open counter screen" 30 | ] 31 | ] 32 | 33 | let render () = 34 | navigationContainer [] [ 35 | Stack.navigator [ 36 | Stack.NavigatorProps.InitialRouteName "home" 37 | ] [ 38 | Stack.screen "home" homePage [] [] 39 | Stack.screen "counter" Counter.counter [ 40 | Stack.ScreenProps.InitialParams ({Initial = None} : Counter.CounterProps) 41 | ] [] 42 | ] 43 | ] 44 | 45 | // Update app name (react-native init ) 46 | Helpers.registerApp "You app name" (render ()) -------------------------------------------------------------------------------- /navigation/App.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /navigation/Counter.fs: -------------------------------------------------------------------------------- 1 | module Counter 2 | 3 | open Fable.ReactNative.Navigation 4 | 5 | type CounterProps = { 6 | Initial : int option 7 | } 8 | 9 | type private Model = { 10 | Navigation : Types.INavigation 11 | Counter : int 12 | } 13 | and private Message = 14 | | Increment 15 | | Decrement 16 | 17 | let private init nav = { 18 | Navigation = nav 19 | Counter = 20 | match nav.route.``params``.Initial with 21 | | None -> 0 22 | | Some i -> i 23 | } 24 | 25 | let private update model msg = 26 | match msg with 27 | | Increment -> {model with Counter = model.Counter + 1 } 28 | | Decrement -> {model with Counter = model.Counter - 1 } 29 | 30 | let private delayedIncrement dispatch = 31 | promise { 32 | do! Promise.sleep 2000 33 | dispatch Increment 34 | } 35 | 36 | module R = Fable.ReactNative.Helpers 37 | module P = Fable.ReactNative.Props 38 | open Fable.ReactNative.Props 39 | 40 | let private buttonStyle : IStyle list = [ 41 | P.Color "#fff" 42 | P.FlexStyle.MarginTop (R.pct 5.) 43 | P.FlexStyle.Padding (R.pct 2.) 44 | P.BorderWidth 1. 45 | P.BorderColor "#fff" 46 | ] 47 | 48 | let private view model dispatch = 49 | R.view [ 50 | P.ViewProperties.Style [ 51 | P.FlexStyle.Flex 1.0 52 | P.FlexStyle.JustifyContent JustifyContent.Center 53 | P.FlexStyle.AlignItems ItemAlignment.Center 54 | P.BackgroundColor "#131313" 55 | ] 56 | ] [ 57 | 58 | R.touchableOpacity [ 59 | OnPress(fun _ -> dispatch Increment) 60 | ] [ 61 | R.text [P.TextProperties.Style buttonStyle] "Increment" 62 | ] 63 | 64 | R.touchableOpacity [ 65 | OnPress(fun _ -> dispatch Decrement) 66 | ] [ 67 | R.text [P.TextProperties.Style buttonStyle ] "Decrement" 68 | ] 69 | 70 | R.touchableOpacity [ 71 | OnPress(fun _ -> 72 | delayedIncrement dispatch 73 | |> Promise.start 74 | ) 75 | ] [ 76 | R.text [P.TextProperties.Style buttonStyle] "Delayed Increment" 77 | ] 78 | 79 | R.text [ 80 | P.TextProperties.Style [ 81 | P.Color "#ffffff" 82 | P.FontSize 30. 83 | P.TextAlign P.TextAlignment.Center 84 | P.FlexStyle.MarginTop (R.pct 4.) 85 | ] 86 | ] (string model.Counter) 87 | 88 | R.touchableOpacity [ 89 | OnPress (fun _ -> 90 | let props = {Initial = Some (model.Counter * 2)} 91 | pushWithData model.Navigation "counter" props 92 | ) 93 | ] [ 94 | R.text [ 95 | P.TextProperties.Style buttonStyle 96 | ] "Open counter again with double current count" 97 | ] 98 | ] 99 | 100 | let counter (navigation : Types.INavigation ) = 101 | Fable.React.FunctionComponent.Of(fun (props : {| nav : Types.INavigation |} ) -> 102 | let initialModel = init props.nav 103 | let model = Fable.React.HookBindings.Hooks.useReducer(update, initialModel) 104 | 105 | view model.current model.update 106 | ) {|nav = navigation |} -------------------------------------------------------------------------------- /navigation/README.md: -------------------------------------------------------------------------------- 1 | # Building apps with F# and Fable with smooth looking navigation 2 | 3 | Even though Elmish is great for building single page apps, it does not play too well with large apps and navigation. If you have ever tried combining Elmish with classic app-navigation, you've probably already ripped half your hair out. Pushing multiple new screens atop your current view while keeping your navigation history and state, quickly becomes way more complicated that it has too be while it looks quirky. 4 | 5 | Using [React Navigation](https://reactnavigation.org) you can add professional navigation handling which looks and feels like the common user expects from a modern app. By combining with React Hooks, like `useReducer`, we can still hold on to a similar MVU-design as we are used to with Elmish. 6 | 7 | This example implements a similar counter app like the Elmish sample. The counter screen is navigable and can be pushed multiple times, with the doubled value of the current counter, while keeping history and screen state. 8 | 9 | ## Setup a new React Native project 10 | 11 | Start a standard new React Native project with `react-native init ` 12 | 13 | ## Setup the F# project 14 | Create a folder to hold your F# project and add the files `App.fsproj`, `App.fs` and `Counter.fs`. In this example i will create a `src` folder in the project root directory which contains the files `App.fsproj` and `App.fs`. 15 | 16 | ## Add npm modules 17 | 18 | In addition to the npm and NuGet setup from the initial how-to you will have to add the following npm-packages for React Navigation. 19 | 20 | `npm install @react-navigation/native @react-navigation/stack @react-navigation/bottom-tabs react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens @react-native-community/masked-view` 21 | 22 | ### `App.fsproj` 23 | ```xml 24 | 25 | 26 | 27 | netstandard2.1 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ``` 41 | 42 | Notice that the Elmish references are removed and a reference to `Fable.ReactNative.Navigation` is added, this will provide us with Fable bindings for the React Navigation library. 43 | 44 | ### `App.fs` 45 | 46 | This app implements a similar counter like the Elmish-based how-to. However, in this example we will start the app at a home page and navigate to the counter using React Navigation. You will need to register your app with the name given at `react-native init` and provide a initial render function. This will register your navigation stack and screens which will be dispatched by React Navigation when navigating through the app. 47 | 48 | Functions rendering from React Navigation calls can have a signature of `unit -> ReactElement` or `INavigation<'T> -> unit` where `'T` is the type of data you expect to be pushed with the screen. The `homePage` function renders out initial home page, here we grab the navigation object as we want to use it to navigate further with it. We do not really care about the properties here. 49 | 50 | ```fsharp 51 | module App 52 | 53 | open Fable.ReactNative.Navigation 54 | 55 | module R = Fable.ReactNative.Helpers 56 | module P = Fable.ReactNative.Props 57 | open Fable.ReactNative.Props 58 | 59 | // render function for the home screen 60 | // the nav argument is added since we will be using it for navigation 61 | // let homePage () = would also work if we did not need the navigaiton prop 62 | let homePage (nav : Types.INavigation<_>) = 63 | R.view [ 64 | P.ViewProperties.Style [ 65 | P.FlexStyle.Flex 1. 66 | P.FlexStyle.JustifyContent JustifyContent.Center 67 | P.FlexStyle.AlignItems ItemAlignment.Center 68 | ] 69 | ] [ 70 | R.text [] "This is the home screen" 71 | 72 | R.touchableOpacity [ 73 | OnPress(fun _ -> 74 | // push a new instance of the counter screen to the stack 75 | nav.navigation.push "counter" 76 | ) 77 | ] [ 78 | R.text [ 79 | P.TextProperties.Style [ 80 | P.FlexStyle.MarginTop (R.pct 5.) 81 | ] 82 | ] "Open counter screen" 83 | ] 84 | ] 85 | 86 | // the initial render function which creates the navigaiton container, 87 | // stack navigator and two stack screens 88 | 89 | // this function will initialize the available screens 90 | // React Navigation will push these to the stack and call the handler function (homePage and Counter.counter) 91 | let render () = 92 | navigationContainer [] [ 93 | Stack.navigator [ 94 | // tell React Navigation to open the home screen initially 95 | Stack.NavigatorProps.InitialRouteName "home" 96 | ] [ 97 | Stack.screen "home" homePage [] [] 98 | Stack.screen "counter" Counter.counter [ 99 | // the counter screen expects a param with an initial counter value 100 | // default is nont 101 | Stack.ScreenProps.InitialParams ({Initial = None} : Counter.CounterProps) 102 | ] [] 103 | ] 104 | ] 105 | 106 | // Update app name (react-native init ) 107 | Helpers.registerApp "You app name" (render ()) 108 | ``` 109 | 110 | ## `Counter.fs` 111 | 112 | We will implement the classic counter sample, as demonstrated in the Elmish-based how-to, using function components and hooks instead of Elmish. Notice how we push a new counter screen with the double of the current counter value. 113 | 114 | ```fsharp 115 | module Counter 116 | 117 | open Fable.ReactNative.Navigation 118 | 119 | type CounterProps = { 120 | Initial : int option 121 | } 122 | 123 | type private Model = { 124 | // Store the navigation object in the model to be user for later 125 | Navigation : Types.INavigation 126 | Counter : int 127 | } 128 | and private Message = 129 | | Increment 130 | | Decrement 131 | 132 | let private init nav = { 133 | Navigation = nav 134 | Counter = 135 | // check the props given with the navigation object for a 136 | // initial counter value 137 | match nav.route.``params``.Initial with 138 | | None -> 0 139 | | Some i -> i 140 | } 141 | 142 | // standard update, but without the Cmd 143 | let private update model msg = 144 | match msg with 145 | | Increment -> {model with Counter = model.Counter + 1 } 146 | | Decrement -> {model with Counter = model.Counter - 1 } 147 | 148 | // a promise that will sleep for 2 sec before dispatching Increment 149 | let private delayedIncrement dispatch = 150 | promise { 151 | do! Promise.sleep 2000 152 | dispatch Increment 153 | } 154 | 155 | module R = Fable.ReactNative.Helpers 156 | module P = Fable.ReactNative.Props 157 | open Fable.ReactNative.Props 158 | 159 | let private buttonStyle : IStyle list = [ 160 | P.Color "#fff" 161 | P.FlexStyle.MarginTop (R.pct 5.) 162 | P.FlexStyle.Padding (R.pct 2.) 163 | P.BorderWidth 1. 164 | P.BorderColor "#fff" 165 | ] 166 | 167 | // standard view 168 | let private view model dispatch = 169 | R.view [ 170 | P.ViewProperties.Style [ 171 | P.FlexStyle.Flex 1.0 172 | P.FlexStyle.JustifyContent JustifyContent.Center 173 | P.FlexStyle.AlignItems ItemAlignment.Center 174 | P.BackgroundColor "#131313" 175 | ] 176 | ] [ 177 | 178 | // increment button 179 | R.touchableOpacity [ 180 | OnPress(fun _ -> dispatch Increment) 181 | ] [ 182 | R.text [P.TextProperties.Style buttonStyle] "Increment" 183 | ] 184 | 185 | //decrement button 186 | R.touchableOpacity [ 187 | OnPress(fun _ -> dispatch Decrement) 188 | ] [ 189 | R.text [P.TextProperties.Style buttonStyle ] "Decrement" 190 | ] 191 | 192 | // delayed increment button 193 | R.touchableOpacity [ 194 | OnPress(fun _ -> 195 | // start promise 196 | delayedIncrement dispatch 197 | |> Promise.start 198 | ) 199 | ] [ 200 | R.text [P.TextProperties.Style buttonStyle] "Delayed Increment" 201 | ] 202 | 203 | // display current counter 204 | R.text [ 205 | P.TextProperties.Style [ 206 | P.Color "#ffffff" 207 | P.FontSize 30. 208 | P.TextAlign P.TextAlignment.Center 209 | P.FlexStyle.MarginTop (R.pct 4.) 210 | ] 211 | ] (string model.Counter) 212 | 213 | // button for pushing new counter screen 214 | R.touchableOpacity [ 215 | OnPress (fun _ -> 216 | // the expected data type 217 | // push the current counter * 2 to the new screen 218 | let props = {Initial = Some (model.Counter * 2)} 219 | 220 | // helper function from Fable.ReactNative.Navigation 221 | // helps push new screens with data 222 | // pushed props to screen "counter" through nav obj from model 223 | pushWithData model.Navigation "counter" props 224 | ) 225 | ] [ 226 | R.text [ 227 | P.TextProperties.Style buttonStyle 228 | ] "Open counter again with double current count" 229 | ] 230 | ] 231 | 232 | 233 | // create a function component with a reducer hook 234 | // the counter function receives the navigation object with props 235 | 236 | let counter (navigation : Types.INavigation ) = 237 | 238 | Fable.React.FunctionComponent.Of(fun (props : {| nav : Types.INavigation |} ) -> 239 | let initialModel = init props.nav 240 | // create a reducer hook 241 | let model = Fable.React.HookBindings.Hooks.useReducer(update, initialModel) 242 | 243 | view model.current model.update 244 | ) {|nav = navigation |} 245 | ``` 246 | 247 | ## Links 248 | - [React Navigation](https://reactnavigation.org) 249 | - [Fable.ReactNative.Navigaion](https://github.com/martinmoec/Fable.ReactNative.Navigation) 250 | - [React Hooks with useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stockfora", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "test": "jest", 10 | "lint": "eslint .", 11 | "build": "fable-splitter -c splitter.config.js --define RELEASE", 12 | "debug": "fable-splitter -c splitter.config.js --define DEBUG", 13 | "watch": "fable-splitter -c splitter.config.js -w --define DEBUG" 14 | }, 15 | "dependencies": { 16 | "@react-native-community/netinfo": "^5.9.6", 17 | "buffer": "^6.0.3", 18 | "react": "17.0.2", 19 | "react-native": "0.64.2" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.14.8", 23 | "@babel/runtime": "^7.14.8", 24 | "@babel/preset-env": "^7.14.8", 25 | "@react-native-community/eslint-config": "^3.0.0", 26 | "babel-jest": "^27.0.6", 27 | "eslint": "^7.31.0", 28 | "jest": "^27.0.6", 29 | "metro-react-native-babel-preset": "^0.66.2", 30 | "react-test-renderer": "17.0.2" 31 | }, 32 | "jest": { 33 | "preset": "react-native" 34 | } 35 | } 36 | --------------------------------------------------------------------------------