Single App for web, iOS, and Android using React-Native ( NEW APPROACH ) – PART 1

anazari96

amir nazari

Posted on January 8, 2022

Single App for web, iOS, and Android using React-Native ( NEW APPROACH ) – PART 1

Single App For All Env

Introduction

Many projects need to implement both web and mobile applications, but there is always hard to decide how they should be developed. Some of them implemented web applications initially, and after that, mobile application development started. Also, some libraries are out there to implement both web and mobile applications together, like React-Native-Web. But the negative point with these libraries is they are not compatible with all other libraries and they make the apps lower. There is a different approach that we can use to satisfy these needs, and that's creating separate UI component for web and mobile, but all of them has a single logical part.

Concept

Before starting to write code, and creating the file structures let me explain the concept of this approach. In React and React-Native one of the best structures is that separating the logic and data parts of every component from the UI part. So we have "Container" and "Screen" for every component in our project. Also as we need to create a project that works in iOS, Android, and Web, we need to separate every UI component into at least 2 parts; "native" and "web". Our UI components will be like "Component.native.ts" and "Component.web.ts".

Maybe it looks like weird these extensions but with this separation, we can use different bundlers for each platform and our purpose will reach out.

In this tutorial, I didn't integrate Redux or any state management to reduce the complexity of the tutorial but as I like Typescript and I'd like to spread the use of this wonderful language, I'll use it in my codes.

This project will be in 2 parts because it's a quite long.

  1. First Part: Create and Configure the project
  2. Second Part: Create some components for different envrionements

Phase 1 (Create a Project):

I prefer to use VSCode as an editor but you can choose any other editor or IDE you want.

Download Visual Studio

First of all, need to create a project, To do so, the React-Native Typescript template should execute in the terminal: (You can change the name of the project by changing the "PROJECT_NAME" in the command below)

$ npx react-native init PROJECT_NAME --template react-native-template-typescript
Enter fullscreen mode Exit fullscreen mode

If you are using Macbook with any M1 chips you will face with CocoaPods error and there are some possibilities to fix the issue:

$ sudo arch -x86_64 gem install ffi

$ arch -x86_64 pod install
Enter fullscreen mode Exit fullscreen mode

You can follow this thread for any other ways that you like to follow for solving this issue:

214

I have a Flutter project that I'm trying to run on iOS. It runs normally on my Intel-based Mac, but on my new Apple Silicon-based M1 Mac it fails to install pods.

LoadError - dlsym(0x7f8926035eb0, Init_ffi_c): symbol not found - /Library/Ruby/Gems/2.6.0/gems/ffi-1.13.1/lib/ffi_c.bundle
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/Library/Ruby/Gems/2.6.0/gems/ffi-1.13.1/lib/ffi.rb:6:in `rescue in <top (required)>'

Phase 2 (Adding Web files):

In this step, we need to add some files to our project for adding the web functionality to it. For this purpose, we are using React library because React is already part of the project and we don't need to do a lot for compatibility.

First of all, we should create a public directory at the root of the project:

$ mkdir public && cd ./public
Enter fullscreen mode Exit fullscreen mode

Public folder stands for keeping the web files like index.html, favicon.ico, manifest.json, and robots.txt. So we need to create these files in the public directory:

$ touch index.html manifest.json robots.txt
Enter fullscreen mode Exit fullscreen mode

index.html

Is the page template. We need to put the following content inside it:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

manifest.json

The manifest.json is a simple JSON file in your website that tells the browser about your website on user's mobile device or desktop. Having a manifest is required by Chrome to show the Add to Home Screen prompt.

We should fill in this file with the below content:

{
  "short_name": "PROJECT_NAME",
  "name": "PROJECT_NAME",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    },
    {
      "src": "logo192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "logo512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}
Enter fullscreen mode Exit fullscreen mode

You should change the PROJECT_NAME with your project name.

As you can see in the contents of the manifest.json we need 3 files; favicon.ico, logo192.png, and logo512.png. You should put your own logo file with these three formats and sizes in the public directory that was created.

robots.txt

A robots.txt file tells search engine crawlers which URLs the crawler can access on your site.

Add this file with the following content:

# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
Enter fullscreen mode Exit fullscreen mode

You can put any pages that the robots should read in front of the Disallow.

index.web.tsx

After that we need to create a start point for the web project. This file will be the place that compiler start to create the hirarchy. The place of this file should be inside the src directory.

$ cd ./src && touch index.web.tsx
Enter fullscreen mode Exit fullscreen mode

Then we need to put some code inside it for rendering our code:

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
  <React.StrictMode>
    {/* Here will be our codes and components */}
  </React.StrictMode>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

After creating these files we need to change some configurations from the project. You can open your editor and open the .eslintrc.js file, then add 'react-app', 'react-app/jest' to the extends key and it should be like:

module.exports = {
  root: true,
  extends: ['@react-native-community', 'react-app', 'react-app/jest'],
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  rules: {
    'no-shadow': 'off',
    '@typescript-eslint/no-shadow': ['error'],
  },
};

Enter fullscreen mode Exit fullscreen mode

Then we need to change 2 property in the tsconfig.json file; change the "lib" property to "dom", "dom.iterable", "esnext" and change the "jsx" property to "preserve".

The file will be like this:

{
  "compilerOptions": {
    /* Basic Options */
    "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
    "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    "lib": ["dom", "dom.iterable", "esnext"], /* Specify library files to be included in the compilation. */
    "allowJs": true, /* Allow javascript files to be compiled. */
    // "checkJs": true, /* Report errors in .js files. */
    "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true, /* Generates corresponding '.d.ts' file. */
    // "sourceMap": true, /* Generates corresponding '.map' file. */
    // "outFile": "./", /* Concatenate and emit output to single file. */
    // "outDir": "./", /* Redirect output structure to the directory. */
    // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "removeComments": true, /* Do not emit comments to output. */
    "noEmit": true, /* Do not emit outputs. */
    // "incremental": true, /* Enable incremental compilation */
    // "importHelpers": true, /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true, /* Enable all strict type-checking options. */
    // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true, /* Enable strict null checks. */
    // "strictFunctionTypes": true, /* Enable strict checking of function types. */
    // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true, /* Report errors on unused locals. */
    // "noUnusedParameters": true, /* Report errors on unused parameters. */
    // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
    // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [], /* List of folders to include type definitions from. */
    // "types": [], /* Type declaration files to be included in compilation. */
    "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
    "skipLibCheck": false, /* Skip type checking of declaration files. */
    "resolveJsonModule": true /* Allows importing modules with a ‘.json’ extension, which is a common practice in node projects. */

    /* Source Map Options */
    // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
  },
  "exclude": [
    "node_modules", "babel.config.js", "metro.config.js", "jest.config.js"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Then last but not least, we need to add some packages to the package.json and add scripts to it.

For supporting the DOM and React scripts we should add the following package to the project.

"react-dom": "^17.0.2","react-scripts": "^5.0.0"

$ npm install react-dom react-scripts
Enter fullscreen mode Exit fullscreen mode

For the development purpose we need some other packages that should be added to the devDependencies inside the package.json. For adding to the dev part we should pass the --save-dev at the end of the command. These are the packages that should be added:

  • "@types/react-dom": "^17.0.11",
  • "@testing-library/jest-dom": "^5.16.1",
  • "@testing-library/react": "^12.1.2",
  • "@testing-library/user-event": "^13.5.0",
  • "@types/node": "^16.11.17",
  • "@types/react": "^17.0.38",

For adding these packhages to you devDependencies execute the following command inside the terminal at the project root folder:

$ npm install @types/react-dom @testing-library/jest-dom @testing-library/react @testing-library/user-event @types/node @types/react --save-dev
Enter fullscreen mode Exit fullscreen mode

After adding the dependencies that we need for the project, it's time to add some additional scripts and change the of the previous ones for the readability in the project. Let's change the scripts part in the package.json file with the following:

"scripts": {
    "build-android": "react-native run-android",
    "build-ios": "react-native run-ios",
    "start-native": "react-native start",
    "test": "jest",
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "start-web": "react-scripts start",
    "build-web": "react-scripts build"
  },
Enter fullscreen mode Exit fullscreen mode

Congratulations! You've finished the configurations of the project. Now you can start the project for the different environments.

For starting on the web:

$ npm start start-web
Enter fullscreen mode Exit fullscreen mode

For starting on the iOS:

$ npm start build-ios
Enter fullscreen mode Exit fullscreen mode

For starting on the Android:

$ npm start build-android
Enter fullscreen mode Exit fullscreen mode

The rest of the article will be in the next article that comes in few days. If you want to know when the new article will be published, subscribe to my Newsletter at my own website!

Subscribe to Newsletter

💖 💪 🙅 🚩
anazari96
amir nazari

Posted on January 8, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related