React Native boilerplate repository version 0.0.1
Make sure you already React Native environment running in you machine. Please refer to the official docs
- Change package name, bundle id (iOS) or application id (android). Reference:
- Create your own keystore and key.properties. Reference:
- Setup your firebase project for firebase analytics, crashlytics, and messaging. Reference:
- Setup your android and ios project to integrate with Fastlane and Firebase Reference:
- Configure your env with react-native-dotenv
- Using Atomic Design Pattern
- Using Redux, Redux Toolkit and RTK Query for services
- Using Native Base UI material to design this boilerplate
NOTES: This boilerplate uses yarn
as main command, not npm
.
To make sure android emulator have the same port with metro bundler, run this command first
yarn android:local
Example how to run android with development env
yarn android:local
yarn run:android-debug
Example how to run android with staging env
yarn android:local
yarn run:android-staging
Example how to run android with production env
yarn android:local
yarn run:android-prod
If there is any error when running these commands, try to run with Android Studio
Example how to build android with prod env release variant
yarn release:android
If there is any error when running this command, try to build with Android Studio
Example how to run ios with development env
yarn run:ios-debug
Example how to run ios with staging env
yarn run:ios-staging
Example how to run ios with production env
yarn run:ios-prod
If there is any error when running these commands, try to run with Xcode
- development
- production
- staging
In android there are 3 product flavor: dev
, prod
, staging
.
In iOS there are 3 scheme: Boilerplate
, Boilerplate Prod
, Boilerplate Staging
.
All of them are already correspond with the env.
Using Semantic Versioning 2.0.0
Major.Minor.Patch
Given a version number MAJOR.MINOR.PATCH, increment the:
- MAJOR version when you make incompatible API changes,
- MINOR version when you add functionality in a backwards compatible manner, and
- PATCH version when you make backwards compatible bug fixes. Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.
camelCase for file and folder.
Using Native-Base Theme, we can create and/or modify existing variant of the component. It also supports dark mode. Here's example of adding dark mode style and add new variant to Button component.
const Button = {
// You can insert dark theme mode here
// mode(first, second) -> first is for light theme, second is for dark theme
baseStyle: (props: any) => {
return {
color: themeTools.mode('red.300', 'blue.300')(props),
};
},
// You can also define or modify existing variant of component here
variants: {
solid: (props: any) => {
return {
_text: {
fontFamily: props.fontFamily,
fontWeight: props.fontWeight,
},
...props,
};
},
outlineLime: (props: any) => {
return {
borderWidth: '1',
borderColor: 'current.100',
_text: {
color: 'current.100',
fontWeight: props.fontWeight,
fontFamily: props.fontFamily,
},
...props,
};
},
},
};
// You need to insert this object to the native-base's extendTheme function
react-native-boilerplate
ββ .buckconfig
ββ .bundle
β ββ config
ββ .commitlintrc.json
ββ .eslintignore
ββ .eslintrc.js
ββ .github
β ββ workflows
β ββ build.yml
ββ .gitignore
ββ .husky
β ββ _
β β ββ .gitignore
β β ββ husky.sh
β ββ commit-msg
β ββ pre-commit
ββ .prettierignore
ββ .prettierrc.js
ββ .ruby-version
ββ .vscode
β ββ .react
β ββ settings.json
ββ .watchmanconfig
ββ CHANGELOG.md
ββ Gemfile
ββ LICENSE
ββ __mocks__
β ββ @react-native-async-storage
β β ββ async-storage.ts
β ββ helpers.tsx
β ββ react-i18next
β β ββ index.js
β ββ timeTravel.ts
β ββ wrapper.tsx
ββ android
β ββ Gemfile
β ββ Gemfile.lock
β ββ fastlane
β β ββ .env
β β ββ Appfile
β β ββ Fastfile
β β ββ Pluginfile
β β ββ README.md
β β ββ report.xml
β ββ gradle
β β ββ wrapper
β β ββ gradle-wrapper.jar
β β ββ gradle-wrapper.properties
β ββ gradle.properties
β ββ gradlew
β ββ gradlew.bat
ββ app.json
ββ babel.config.json
ββ index.js
ββ ios
β ββ Config.xcconfig
β ββ Gemfile
β ββ Gemfile.lock
β ββ Podfile
β ββ Podfile.lock
β ββ fastlane
β ββ Appfile
β ββ Fastfile
β ββ Pluginfile
ββ jest.config.js
ββ jest.setup.js
ββ metro.config.js
ββ package.json
ββ react-native.config.js
ββ sonar-project.properties
ββ src
β ββ __tests__
β β ββ App.test.tsx
β ββ assets
β β ββ fonts
β β β ββ OpenSans
β β β β ββ OpenSans-Bold.ttf
β β β β ββ OpenSans-BoldItalic.ttf
β β β β ββ OpenSans-ExtraBold.ttf
β β β β ββ OpenSans-ExtraBoldItalic.ttf
β β β β ββ OpenSans-Italic.ttf
β β β β ββ OpenSans-Light.ttf
β β β β ββ OpenSans-LightItalic.ttf
β β β β ββ OpenSans-Medium.ttf
β β β β ββ OpenSans-MediumItalic.ttf
β β β β ββ OpenSans-Regular.ttf
β β β β ββ OpenSans-SemiBold.ttf
β β β β ββ OpenSans-SemiBoldItalic.ttf
β β β ββ Poppins
β β β ββ OFL.txt
β β β ββ Poppins-Black.ttf
β β β ββ Poppins-BlackItalic.ttf
β β β ββ Poppins-Bold.ttf
β β β ββ Poppins-BoldItalic.ttf
β β β ββ Poppins-ExtraBold.ttf
β β β ββ Poppins-ExtraBoldItalic.ttf
β β β ββ Poppins-ExtraLight.ttf
β β β ββ Poppins-ExtraLightItalic.ttf
β β β ββ Poppins-Italic.ttf
β β β ββ Poppins-Light.ttf
β β β ββ Poppins-LightItalic.ttf
β β β ββ Poppins-Medium.ttf
β β β ββ Poppins-MediumItalic.ttf
β β β ββ Poppins-Regular.ttf
β β β ββ Poppins-SemiBold.ttf
β β β ββ Poppins-SemiBoldItalic.ttf
β β β ββ Poppins-Thin.ttf
β β β ββ Poppins-ThinItalic.ttf
β β ββ images
β β β ββ common
β β β β ββ index.ts
β β β β ββ logo.png
β β β β ββ logo.svg
β β β ββ index.ts
β β ββ index.ts
β ββ components
β β ββ atoms
β β β ββ index.ts
β β ββ containers
β β β ββ index.ts
β β ββ index.ts
β β ββ molecules
β β β ββ ProductsCard
β β β β ββ index.tsx
β β β ββ UsersCard
β β β β ββ index.tsx
β β β ββ index.ts
β β ββ organisms
β β ββ FOProductsSection
β β β ββ index.tsx
β β ββ FOUserSection
β β β ββ index.tsx
β β ββ index.ts
β ββ config
β β ββ config.d.ts
β β ββ constant.ts
β β ββ index.ts
β β ββ setting.ts
β β ββ url.ts
β ββ global
β β ββ env.d.ts
β β ββ index.d.ts
β β ββ navigation.d.ts
β ββ hooks
β β ββ __tests__
β β β ββ useLayout.test.ts
β β ββ index.ts
β β ββ useDebounce.ts
β β ββ useLayout.ts
β ββ index.tsx
β ββ lang
β β ββ en.json
β β ββ id.json
β ββ navigation
β β ββ __tests__
β β β ββ navigation.test.tsx
β β ββ index.tsx
β β ββ navigationService.ts
β ββ redux
β β ββ __tests__
β β β ββ application.test.ts
β β ββ application
β β β ββ index.ts
β β β ββ types.d.ts
β β ββ index.ts
β β ββ rootReducer.ts
β ββ screens
β β ββ Home
β β β ββ hook.ts
β β β ββ index.tsx
β β β ββ styles.ts
β β β ββ types.d.ts
β β ββ Setting
β β β ββ hook.ts
β β β ββ index.tsx
β β β ββ styles.ts
β β β ββ types.d.ts
β β ββ SignIn
β β β ββ hook.ts
β β β ββ index.tsx
β β β ββ styles.ts
β β β ββ types.d.ts
β β ββ Splash
β β β ββ hook.ts
β β β ββ index.tsx
β β β ββ styles.ts
β β β ββ types.d.ts
β β ββ __tests__
β β β ββ Setting.test.tsx
β β β ββ SignIn.test.tsx
β β β ββ Splash.test.tsx
β β ββ index.tsx
β ββ services
β β ββ baseQuery.ts
β β ββ index.ts
β β ββ products
β β β ββ index.ts
β β ββ user
β β ββ index.ts
β ββ theme
β β ββ __tests__
β β β ββ components.test.ts
β β β ββ fonts.test.ts
β β ββ baseStyle.ts
β β ββ colors.ts
β β ββ components.ts
β β ββ fonts.ts
β β ββ index.ts
β ββ utils
β ββ __tests__
β β ββ generic.test.ts
β β ββ normalize.test.ts
β ββ generic.ts
β ββ normalize.ts
ββ tsconfig.json
ββ yarn.lock
A brief description of the layout:
.github
has one github workflows directory.android
is android configuration directory.ios
is ios configuration directory..gitignore
varies per project, but most of it uses create react-native app base .gitignore file.
You can see the example in src/services/products
. This is the only basic api to get data from API, you can learn more from the official docs.
import { createApi } from '@reduxjs/toolkit/dist/query/react';
import { functionsBaseQuery } from '../baseQuery';
export interface IProductsDetail {
id: number;
title: string;
description: string;
price: number;
discountPercentage: number;
rating: number;
stock: number;
brand: string;
category: string;
thumbnail: string;
images: string[];
}
export interface IProducts {
products: IProductsDetail[];
total: number;
skip: number;
limit: number;
}
const reducerPath = 'productsAPI';
export const productsAPI = createApi({
reducerPath: reducerPath,
baseQuery: functionsBaseQuery(),
tagTypes: ['Products'], //Provide tags that are available for this api
keepUnusedDataFor: process.env.NODE_ENV !== 'test' ? 60 : 0,
endpoints: (builder) => ({
getProduct: builder.query<IProducts, string>({ //Rule of thumb, query is used for GET method
query: (query) => `/products/search?q=${query}`,
providesTags: ['Products'], //Provide the corresponding tags from tagTypes
}),
refetchProducts: builder.mutation<null, void>({ //Rule of thumb, mutation is used for POST, PATCH, DELETE method
// The query is not relevant here, so a `null` returning `queryFn` is used
queryFn: () => ({ data: null }),
// This mutation takes advantage of tag invalidation behaviour to trigger
// any queries that provide the 'Post' or 'User' tags to re-fetch if the queries
// are currently subscribed to the cached data.
// Meaning if we call this, it will call getProduct to update the cached data
invalidatesTags: ['Products'],
}),
}),
});
export const { useGetProductQuery } = productsAPI;
export const productsQueryReducer = { [reducerPath]: productsAPI.reducer };
This is only for android. To upload a "beta" version to firebase, you can run command
android:beta
Unit testing uses jest. You can navigate to here to see some unit test examples
Run yarn test:cov
and it will generate coverage report on .coverage folder
This boilerplate is already supported with Detox. You can navigate here to see some examples. All available configs are on .detoxrc.json
file.
You need to change your simulator and emulator name on the .detoxrc.json
configuration first to be same with the one on your machine so it can work on your machine.
To build and test application run on ios
build:e2e-ios-dev-debug
test:e2e-ios-dev-debug
To build and test application run on android
build:e2e-android-dev-debug
test:e2e-android-dev-debug
You can see the example in src/services/__tests__/products.test.ts
based on this article.
import { AllTheProviders } from '../../__mocks__/utils/wrapper';
import { renderHook } from '@testing-library/react-hooks/native';
import { useGetProductQuery } from '../../services';
import { products } from '../../__mocks__/testData';
const updateTimeout = 5000;
describe('FOProductsSection screen', () => {
it('handles good response', async () => {
fetchMock.mockResponse(JSON.stringify({ data: products }));
const { result, waitForNextUpdate } = renderHook(() => useGetProductQuery(undefined), {
wrapper: AllTheProviders,
});
const initialResponse = result.current;
expect(initialResponse.data).toBeUndefined();
expect(initialResponse.isLoading).toBe(true);
await waitForNextUpdate({ timeout: updateTimeout });
const nextResponse = result.current;
expect(nextResponse.data).toBeDefined();
expect(nextResponse.isLoading).toBe(false);
expect(nextResponse.isSuccess).toBe(true);
});
it('handles error response', async () => {
fetchMock.mockReject(new Error('Internal Server Error'));
const { result, waitForNextUpdate } = renderHook(() => useGetProductQuery(undefined), {
wrapper: AllTheProviders,
});
const initialResponse = result.current;
expect(initialResponse.data).toBeUndefined();
expect(initialResponse.isLoading).toBe(true);
await waitForNextUpdate({ timeout: updateTimeout });
const nextResponse = result.current;
expect(nextResponse.data).toBeUndefined();
expect(nextResponse.isLoading).toBe(false);
expect(nextResponse.isError).toBe(true);
});
});
You can see the example in src/components/organisms/__tests__/FOProductsSection.test.tsx
based on this article.
import React from 'react';
import * as hooks from '../../../services/products';
import FOProductsSection from '../FOProductsSection';
import { render } from '../../../__mocks__/utils/wrapper';
import { products } from '../../../__mocks__/testData';
describe('FOProductsSection screen', () => {
it('can shows 4 data correctly', async () => {
jest.spyOn(hooks, 'useGetProductQuery').mockReturnValue({
data: products,
isError: false,
isLoading: false,
refetch: function (): void {
throw new Error('Function not implemented.');
},
});
const { findAllByTestId } = render(<FOProductsSection query={'Apple'} />);
expect((await findAllByTestId('FMProductsCard')).length).toBe(4);
});
});