Share same code for web and mobile application part 1

Share same code for web and mobile application with Nx Workspace, react-native-web, and Native Base

In this article, we will start with Nx Workspace to set up a monorepo that will contain the Next JS web application and React Native app with the expo. Then we will add configuration for react-native-web in the NextJS app which will help us to use react-native’s components and APIs which are interoperable with React DOM. Later we will take advantage of Nx to create our shared UI component lib for both Next and React Native apps. And Part 2 we will see how we can use Native Base which has Universal Components for React & React Native.

Nx Installation

First, we need to install Nx for that we will use the below command.

npm install create-nx-workspace

Creating a workspace and adding apps

Creating Next JS web app

Open a terminal and run the following command. Here we are passing --preset=next so it will also generate workspace with Next JS app.

npx create-nx-workspace --preset=next

After running the command this will prompt us to some configuration options which we need to provide.

e.g.

  • Workspace name: shared-code-demo (project name)

  • Application name: web-app (Our web app name)

  • Default stylesheet format: CSS

  • Use Nx Cloud: No

configuration options

After the creation of the project, we can see a project structure like this

.
├── README.md
├── apps
├── babel.config.json
├── dist
├── jest.config.ts
├── jest.preset.js
├── libs
├── nx.json
├── package-lock.json
├── package.json
├── tools
├── tsconfig.base.json
└── workspace.json

apps: will contain our web and mobile app
libs: will contain the shared code components lib or any lib that we will create

Let's try running the NextJS app now.

npm start

after running the above command it will open the Next app on http://localhost:4200.

Default NX NextJS app

Creating React Native app with Expo

First, we need to install @nrwl/expo package as dev dependencies. So run the following command in the workspace project root.

npm install @nrwl/expo -D

After successful installation of this package, we can generate a react-native app using the following command

//here mobile-app is the name of our expo app 

npx nx generate @nrwl/expo:application mobile-app

Running the react-native app

To run the app we need to add scripts in the project root package.json

"scripts": { 
... 
"start:ios": "nx run mobile-app:run-ios", 
"start:android": "nx run mobile-app:run-android" 
}

Now in the project root run any of the above scripts we added to run the mobile app.

npm run start:ios

After running the command it will ask for the bundle identifier so provide that

Bundle identifier

after the app builds you will see the app running.

So far we are able to set up monorepo and add our mobile and web project next we will explore how we can use react-native in our web project.

Adding react-native-web in NextJS project.

Let's start with adding some dependencies (Run the following commands in the workspace project root.)

npm install react-native-web 
npm install --save-dev babel-plugin-react-native-web

Open next.config.js file which is present in our NextJS app. Here we will add the webpack config to configure the bundler to alias the package to react-native.

//apps/web-app/next.config.js

// eslint-disable-next-line @typescript-eslint/no-var-requires
const withNx = require('@nrwl/next/plugins/with-nx');

/**
 * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions}
 **/
const nextConfig = {
  nx: {
    // Set this to true if you would like to to use SVGR
    // See: https://github.com/gregberge/svgr
    svgr: false,
  },
  webpack: (config) => {
    config.resolve.alias = {
      ...(config.resolve.alias || {}),
      // Transform all direct `react-native` imports to `react-native-web`
      'react-native$': 'react-native-web',
    };
    config.resolve.extensions = [
      '.web.js',
      '.web.ts',
      '.web.tsx',
      ...config.resolve.extensions,
    ];
    return config;
  },
};

module.exports = withNx(nextConfig);

after that will create the file babel.config.js inside the root of Nextjs app and add the following code

//apps/web-app/babel.config.js

module.exports = {
  presets: ['@nrwl/next/babel'],
  plugins: [['react-native-web', { commonjs: true }]],
};

With all the above setup in the webpack config and babel file all direct imports of react-native will tansform to react-native-web which can be useful when we are using components and APIs from react-native to build a web application.

Now to check this setup is working we can open the next app pages index file and try the following code.

//apps/web-app/pages/index.tsx

import { Text } from 'react-native';

export function Index() {
  return <Text>Text Element imported from react-native</Text>;
}

export default Index;

Now we can run our Next app and see this Text element is rendered on screen.

We configured react-native-web and used react-native element in our Next js project.

Creating UI lib to share components between web and mobile

In Nx, we can add different types of libraries in a workspace based on the business logic, presentational components, state management, and utilities. Here we will create a lib for our shared common React components which we will use in react-native and next app. For that run following in workspace root

 npx nx generate @nrwl/react:library shared-components --style=none

here shared-components is the name of lib which we are creating and --style=none means don't create any style files.

After execution of the command if we expand the libs folder we can see shared-components folder is generated which looks like

.
├── shared-components
│   ├── README.md
│   ├── jest.config.ts
│   ├── project.json
│   ├── src
│   │   ├── index.ts
│   │   └── lib
│   │       ├── shared-components.spec.tsx
│   │       └── shared-components.tsx
│   ├── tsconfig.json
│   ├── tsconfig.lib.json
│   └── tsconfig.spec.json

we can delete existing files inside libs/shared-components/src/lib as we will see how to generate our own components.

Generate component in lib

Nx also provides a functionality by which we can generate component files by executing a command

npx nx generate @nrwl/react:component TextMessage --project=shared-components --style=none --export

in the above command TextMessage is a component name that we want to generate. --project=shared-components means we want to generate that component inside shared-components lib. After successful execution of the command, we will see our component file generated in the libs folder as below

.
├── src
│   ├── index.ts
│   └── lib
│       └── text-message
│           ├── text-message.spec.tsx
│           └── text-message.tsx

Now we will add code in the generated file text-message.tsx

import { Text } from 'react-native';

export interface TextMessageProps {
  message: string;
}

export function TextMessage(props: TextMessageProps) {
  return (
    <Text style={{ padding: 10, textAlign: 'center', fontSize: 30 }}>
      {props.message}
    </Text>
  );
}

export default TextMessage;

here we created a component TextMessage which will take a message as a prop and return a Text element from react-native.

Next, we will use this same component in both mobile and web apps to do that make changes in the respective files mentioned below

In Next JS app

Open apps/web-app/pages/index.tsx and add below code

import React from 'react';
import { TextMessage } from '@shared-code-demo/shared-components';
export function Index() {
  return (
    <>
      <TextMessage message="TextMessage In Next App" />
    </>
  );
}

export default Index;

In React-Native app Open apps/mobile-app/src/app/App.tsx and add below code

import React from 'react';
import { SafeAreaView, StatusBar } from 'react-native';
import { TextMessage } from '@shared-code-demo/shared-components';

const App = () => {
  return (
     <>
      <StatusBar />
      <SafeAreaView>
          <TextMessage message="TextMessage In React Native App" />
      </SafeAreaView>
    </>
  );
};

export default App;

Now if we run both apps we can see using the same components on different platforms.

Result

Conclusion

We explored how we can create a monorepo with the help of Nx workspace and how we can add applications to our workspace. Next, we saw how we can configure react-native-web in Next js. And at the end, we created a shared component lib and used the same component in React-Native and Next js app.

In the next part, we will explore how we can integrate native-base in our setup and take advantage of pre-built components for both mobile and web platforms.