Step by Step Guide on Creating a Monorepo for React Native Apps using Nx
source link: https://blog.nrwl.io/step-by-step-guide-on-creating-a-monorepo-for-react-native-apps-using-nx-704753b6c70e
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Step by Step Guide on Creating a Monorepo for React Native Apps using Nx
Do you want to have both mobile and web apps in the same repo? Do you wish that you could share code between mobile and web apps? This blog post shows you how to create a React Native mobile app and a React web app in the same repo with shared libraries using Nx.
It goes through how to create a simple 2-page app that shows your daily horoscope based on your zodiac sign.
TL;DR — GitHub repo:
GitHub page: https://xiongemi.github.io/aztro-daily-horoscope/
Overview
Tech Stack
- Monorepo: Nx
- Mobile Frontend: React Native
- Web Frontend: React
- UI Library: React Native Elements
- State Management: Redux
- Navigation: React Navigation
- API: aztro
Setup
For iOS and Android development environment setup, please see https://reactnative.dev/docs/environment-setup.
To create an Nx workspace, run:
npx create-nx-workspace aztro-daily-horoscope --preset=react-native
To generation the application daily-horoscope-app
, run:
nx generate application daily-horoscope-app
This should generate daily-horoscope-app
folder under apps:
daily-horoscope-app
folderNow you install the starter project of Nx React Native. If you run:
nx run-ios daily-horoscope-app
, it should launch the app in the iOS simulator.nx run-android daily-horoscope-app
, it should launch the app in the Android simulator.
Install React Native Elements
This example uses React Native Elements as its UI component library and react-native-vector-icons as its icon library.
To install, run:
# npm
npm install --save react-native-elements react-native-vector-icons react-native-safe-area-context# yarn
yarn add react-native-elements react-native-vector-icons react-native-safe-area-context
In the app’s package.json at apps/daily-horoscope-app/package.json
, under dependencies, add the above packages:
{
"name": "daily-horoscope-app",
"version": "0.0.1",
"private": true,
"dependencies": {
... "react-native-elements": "*",
"react-native-safe-area-context": "*",
"react-native-vector-icons": "*"
}
}
Install react-native-vector-icons for iOS
There are additional steps needed to add the icon font files to the iOS and Android bundle.
In apps/daily-horoscope-app/ios/Podfile
file, add below line before post_install
:
pod 'RNVectorIcons', :path => '../../../node_modules/react-native-vector-icons'
In apps/daily-horoscope-app/ios/DailyHoroscope/Info.plist
file, add below key array pair before the closing </dict>
tag:
<key>UIAppFonts</key>
<array>
<string>AntDesign.ttf</string>
<string>Entypo.ttf</string>
<string>EvilIcons.ttf</string>
<string>Feather.ttf</string>
<string>FontAwesome.ttf</string>
<string>FontAwesome5_Brands.ttf</string>
<string>FontAwesome5_Regular.ttf</string>
<string>FontAwesome5_Solid.ttf</string>
<string>Foundation.ttf</string>
<string>Ionicons.ttf</string>
<string>MaterialIcons.ttf</string>
<string>MaterialCommunityIcons.ttf</string>
<string>SimpleLineIcons.ttf</string>
<string>Octicons.ttf</string>
<string>Zocial.ttf</string>
<string>Fontisto.ttf</string>
</array>
Go to the iOS folder at apps/daily-horoscope-app/ios
and run Pod install
. After running the command, you should see there are some changes in Podfile.lock
file.
Install react-native-vector-icons for Android
In file apps/daily-horoscope-app/android/app/build.gradle
, add below line at end of the file:
apply from: "../../../../node_modules/react-native-vector-icons/fonts.gradle"
Great! You have installed the UI component library and icon library.
Create a Page in React Native
Now, you need to create the first page of your app: a page that shows a list of all the zodiac signs and allows users to choose from it.
Create Models
First, create a library for the models:
nx generate lib models
This should generate a models folder under libs:
Then under this models folder, create a file to have the below enum that contains all the zodiac signs:
export enum AdhZodiacSign {
Aries = 'Aries',
Taurus = 'Taurus',
Gemini = 'Gemini',
Cancer = 'Cancer',
Leo = 'Leo',
Virgo = 'Virgo',
Libra = 'Libra',
Scorpio = 'Scorpio',
Sagittarius = 'Sagittarius',
Capricorn = 'Capricorn',
Aquarius = 'Aquarius',
Pisces = 'Pisces',
}
Note: the enum has a prefix “Adh” to indicate it is a model under domain “aztro-daily-horoscope”. Add this prefix to distinguish model names from component names.
This example uses icons from Material Community Icons. You need to create a list that contains the zodiac sign name and its matching icon.
Create a Component for Zodiac Sign List
Then, create a library for the UI and create a component zodiac-sign-list
:
nx generate lib ui
nx generate component zodiac-sign-list --project=ui --export
This generates the folder zodiac-sign-list
under ui/src/lib
.
In the libs/ui/src/lib/zodiac-sign-list/zodiac-sign-list.tsx
file, add below code. It uses the FlatList component from react-native and the ListItem component from react-native-elements. It is going to passAdhZodiacSignList
from themodels
library you created above to the FlatList component.
In the apps/daily-horoscope-app/src/app/App.tsx
file, you could now use the above zodiac-sign-list
component:
zodiac-sign-list
If you run nx run-ios daily-horoscope-app
and nx run-android daily-horoscope-app
, you should see something like:
You have created the first page of your app.
If you run the command nx dep-graph
, you should see what the dependency graph looks like below:
The next step is to handle action when users pressed on a list item. To achieve that, it is going to use Redux.
Add Redux
This example uses Redux as state management.
Now add a library called store
:
nx generate lib store
Create a State for Horoscope
Run command to create redux state for horoscope:
nx generate @nrwl/react:redux horoscope --project=store --directory=horoscope
In terminal, it should output:
CREATE libs/store/src/lib/horoscope/horoscope.slice.spec.ts
CREATE libs/store/src/lib/horoscope/horoscope.slice.ts
.slice
file is an extension introduced by Redux Toolkit. It is just a shorthand way to create actions + reducers + selectors in one file.
Notice in the package.json, this command also adds packages: @reduxjs/toolkit and react-redux.
Next, you are going to add a new value zodiacSignItem
in the HoroscopeState
to store the zodiac sign user selected. In the libs/store/src/lib/horoscope/horoscope.slice.ts
file, the HoroscopeState
will become:
export interface HoroscopeState {
loadingStatus: 'not loaded' | 'loading' | 'loaded' | 'error';
error?: string;
zodiacSignItem?: AdhZodiacSignItem;
}
Under horoscopeSlice
, add an action to the reducers to change zodiacSignItem
in the state:
export const horoscopeSlice = createSlice({
name: HOROSCOPE_FEATURE_KEY,
initialState: initialHoroscopeState,
reducers: {
setUserZodiacSignItem(
state: HoroscopeState,
action: PayloadAction<AdhZodiacSignItem>
) {
state.zodiacSignItem = action.payload;
},
...
},
...
});
Create Root Store
Now you need to setup up the root reducer and configure the store. Create a root folder under libs/store/src/lib
and add the below files:
Dispatch Action from zodiac-sign-list Component
You now need to dispatch setUserZodiacSignItem
action from libs/ui/src/lib/zodiac-sign-list/zodiac-sign-list.tsx
component.
Create a file atlibs/ui/src/lib/zodiac-sign-list/zodiac-sign-list.props.ts
, and add a function mapDispatchToProps
. In this function, dispatch the setUserZodiacSignItem
action.
The component zodiac-sign-list
needs to import from the above zodiac-sign-list.props
file. Then the component zodiac-sign-list
becomes:
Notice that a container component is added. This container component is stateful that connects to the redux state.
export const ZodiacSignListContainer = connect(
null,
mapDispatchToProps
)(ZodiacSignList);
This container component passes down props ZodiacSignListProps
to ZodiacSignList
. So this prop setUserZodiacSignItem
got called at the press event for ListItem: onPress={() => setUserZodiacSignItem(item)}
.
Add Provider to App
Go back to the app file at apps/daily-horoscope-app/src/app/App.tsx
, now you need to replace the ZodiacSignList
with ZodiacSignListContainer
, and add the provider for the root store.
Awesome! So every time a zodiac sign item in the list got pressed, action gets dispatched and it should update the state with the zodiac sign selected.
Debugging Redux
First, you need to install redux-logger:
# npm
npm install --save-dev redux-logger @types/redux-logger# yarn
yarn add redux-logger @types/redux-logger --dev
Then you need to add the redux-logger to root store’s middleware, so the rootStore becomes:
import logger from 'redux-logger';const rootStore = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => isDevelopment ? getDefaultMiddleware().concat(logger) : getDefaultMiddleware(),
devTools: isDevelopment,
preloadedState: initialRootState,
});
Since the code is running in simulators, how to use the Redux Devtools extension and view the Redux Logger?
Open the debug menu in the simulator by entering d
in the terminal that runs the start command. Then in the debug menu, choose “Debug with Chrome” for iOS and “Debug” for Android.
Install tool React Native Debugger: https://github.com/jhen0409/react-native-debugger.
Now inside React Native Debugger, you should be able to use Redux Devtools and Redux Logger. Now if you press any zodiac sign from the list, you should see action horoscope/setUserZodiacSignItem
got dispatched and the state is updated.
Now you have successfully set up the Redux store for your app. The next step is to navigate to a different screen when you have successfully selected a zodiac sign.
Adding Navigation
Setup
To add navigation, you need to install React Navigation library:
# npm
npm install --save @react-navigation/native @react-navigation/stack react-native-reanimated react-native-gesture-handler react-native-screens @react-native-community/masked-view# yarn
yarn add @react-navigation/native @react-navigation/stack react-native-reanimated react-native-gesture-handler react-native-screens @react-native-community/masked-view
In the app’s package.json at apps/daily-horoscope-app/package.json
, under dependencies, add the above packages:
{
"name": "daily-horoscope-app",
"version": "0.0.1",
"private": true,
"dependencies": {
... "@react-native-masked-view/masked-view": "*",
"@react-navigation/native": "*",
"@react-navigation/stack": "*",
"react-native-gesture-handler": "*",
"react-native-reanimated": "*",
"react-native-screens": "*"
}
}
Go to the iOS folder at apps/daily-horoscope-app/ios/Podfile
and run Pod install
.
In apps/daily-horoscope-app/src/main.tsx
file, add below line at top of the file:
import 'react-native-gesture-handler';
Update App to Use React Navigation
Now you need to update the apps/daily-horoscope-app/src/app/App.tsx
file to use React Navigation. Instead of displaying ZodiacSignListContainer
component directly, now it is going to be passed to Stack.Screen
. The apps/daily-horoscope-app/src/app/App.tsx
file looks like below:
If you run the code in the simulator, the app should look similar to before except for the header.
Create Second Page
Now you need to create the 2nd page to be navigated. Create a component called horoscope-card
under ui:
nx generate component horoscope-card --project=ui --export
This should generate libs/ui/src/lib/horoscope-card
folder.
Add the below code to libs/ui/src/lib/horoscope-card/horoscope-card.tsx
. For now, this component is going to use mock static data. It just displays a title for the zodiac Leo. It uses the Card component from react-native-elements.
Horoscope Card with static data
Navigate Between Screens
Now you need to add this screen to your app. In file apps/daily-horoscope-app/src/app/App.tsx
, you need to update it to add a stack screen for the horoscope-card
component:
In the libs/ui/src/lib/zodiac-sign-list/zodiac-sign-list.tsx
, you need to trigger navigation when the list item got pressed.
Below code uses useNavigation
hook from React Navigation library. When the list item got pressed, it is going to call navigation.navigate(‘Horoscope Card’)
to navigate the horoscope-card
component you created above.
https://gist.github.com/xiongemi/c78c719e70aa4948b98e68033d7fe4a3
Now you should be able to navigate between 2 screens.
Integrate with API
Now you need to updatehoroscope-card
with real data. This example uses a free and open API: https://github.com/sameerkumar18/aztro.
Add Services
First, generate a library called services:
nx generate lib services
In the services folder, add the below files:
aztro.service.ts
calls the API to get the user’s horoscope based on the zodiac sign and day.aztro-horoscope-response.interface.ts
defines what the response object looks like. It has a transform function to transform response data to the app domain model.
You also need to add 2 more files to the models
library:
horoscope-day.type.ts
defines the allowed day value to pass to API.horoscope.interface.ts
is the app domain interface that is transformed from the API response data.
Connect to Redux
Now you need to create action to call aztro.service
and store its response to the redux state.
Now you need to update the interface for the horoscope state value in file libs/store/src/lib/horoscope/horoscope.slice.ts
:
loadingStatus
is the API request status fromaztro.service
.error
is the API request error fromaztro.service
.zodiacSignItem
is the user’s selected zodiac sign.day
is the parameter passed toaztro.service
.horoscope
is the transformed response fromaztro.service.
Then, you need to update this file to add a thunk action and reducers to fetch the horoscope:
Actions and reducers to fetch horoscopeIt adds a thunk action and its corresponding reducers:
fetchHoroscope
is going to callaztro.service
.fetchHoroscope.pending
is dispatched whenfetchHoroscope
is triggeredfetchHoroscope.fulfilled
is dispatched whenaztro.service
returns a successful response. It is going to assign thestate.horoscope
with its responsefetchHoroscope.rejected
is dispatched whenaztro.service
returns a failed response.
Now you need to pass the redux state value to your horoscope-card
component. Add below selectors to this file. These are pure functions that take the root state as input and derive data from it:
To summarize, the file libs/store/src/lib/horoscope/horoscope.slice.ts
will become like below:
Then update horoscope-card
component with the below code. Notice inside mapStateToProps
function, it uses selector functions from above.
Notice inside the horoscope-card
component, it has a hook to dispatch action getUserHoroscope
when this component got mounted.
useEffect(() => {
if (zodiacItem?.zodiacSign) {
getUserHoroscope(zodiacItem.zodiacSign, 'today');
}
}, [zodiacItem, getUserHoroscope]);
In the App component, replace HoroscopeCard
with HoroscopeCardContainer
:
<Stack.Screen
name="Horoscope Card"
component={HoroscopeCardContainer}
/>
Now when you run the app, it should display the horoscope according to the zodiac user selected.
Finally, you got a mobile app that runs on both Android and iOS. You could reuse the libraries to create a web app.
If you run command nx dep-graph
, you should see the dependency graph looks like below:
Create Web App
First, generate a React app called daily-horoscope-app
:
nx generate @nrwl/react:app daily-horoscope-app
You could reuse store
, models
, and services
libraries and write a separate ui for the React web app. However, this example just reuses ui
library and displays React Native components directly. To do so, it needs to install package react-native-web:
# npm
npm install --save react-native-web
npm install --save-dev babel-plugin-react-native-web# yarn
yarn add react-native-web
yarn add --dev babel-plugin-react-native-web
For apps/daily-horoscope-web/src/main.tsx
, change it to:
import { AppRegistry } from 'react-native';import App from './app/app';AppRegistry.registerComponent('main', () => App);
AppRegistry.runApplication('main', {
rootTag: document.getElementById('root'),
});
Copy your code from daily-horoscope-app
’s app file to daily-horoscope-web
’s app file and add styles for the icon font files:
Then you need a customized Webpack file. It adds 2 additional rules to read the icon font files and react-native-elements library files.
Also in workspace.json
, change the webpackConfig under daily-horoscope-web to point this custom wepback file like:
"webpackConfig": "apps/daily-horoscope-web/webpack.js"
Now if you run nx serve daily-horoscope-web
, it should the web app in the browser.
Now the dependency graph should look like:
Conclusion
Congratulations! You have created a React Native mobile app and a React web app. Nx + React + React Native is a powerful tool that allows you to have both mobile and web app code in the same repo with shared business logic.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK