RNJet LogoRNJet
Architecture

Tech Stack — React Native New Architecture, MMKV, TanStack Query & More

All packages included in RNJet — React Navigation, TanStack Query, Axios, MMKV, i18next, react-native-config. All support React Native's New Architecture (Fabric + JSI).

Tech Stack

RNJet is built on a carefully selected set of packages that cover everything a production React Native app needs. All packages support React Native's New Architecture (Fabric + JSI).


Core

React Native + TypeScript

TypeScript is configured out of the box. You get full IDE support, type safety, and a better developer experience from day one — no extra setup required.


RNJet uses React Navigation with a nested stack structure. The app is split into two root stacks — CommonStack for onboarding screens, and MainStack for the main app.

Navigator (root)
├── Common → CommonStack
└── Main   → MainStack

Adding a New Screen

1. Add the screen to the stack's ParamList:

// src/navigation/navigator/stack/main-stack/MainStackParamList.d.ts
type ProfileStackParamList {
  userId?: string;
}
type MainStackParamList = {
  HomeScreen: undefined;
  ProfileScreen: ProfileStackParamList; // ← add here if you need to pass params to the screen
};

2. Register the screen in the stack:

// src/navigation/navigator/stack/main-stack/MainStack.tsx
import { ProfileScreen } from "@modules";

<Stack.Screen name="ProfileScreen" component={ProfileScreen} />;

That's it — the screen is now navigable.

Adding a New Stack

If you need a new group of screens (e.g. a auth flow), create a new stack and register it in the root ParamList and Navigator:

1. Add to the root ParamList:

// src/navigation/navigator/screen.ts
export type ParamList = {
	Common: NavigatorScreenParams<CommonStackParamList>;
	Main: NavigatorScreenParams<MainStackParamList>;
	Auth: NavigatorScreenParams<AuthStackParamList>; // ← add here
};

2. Register in Navigator:

// src/navigation/navigator/Navigator.tsx
<Stack.Screen name="Auth" component={AuthStack} />

Use the useNavigate hook — never call useNavigation() directly:

import { useNavigate } from "@/hooks";

const { navigateScreen, popScreen, resetNavigate, getRouteParams } =
	useNavigate();

Go to a screen:

// Screen in Main stack
navigateScreen("Main", { screen: "HomeScreen" });

// Screen with params
navigateScreen("Main", { screen: "ProfileScreen", params: { userId: "123" } });

Go back:

popScreen();

Reset navigation (e.g. after login/logout):

resetNavigate("Main", { screen: "HomeScreen" });

Get params from the current screen:

const { userId } = getRouteParams<ProfileStackParamList>();

Data Fetching

RNJet combines TanStack Query for server state management with a custom Axios-based API layer that handles logging, retries, and token auth out of the box.

API Functions

All API calls go through wrapper functions in src/api/. There are two clients — client (with auth token) and clientWithoutToken — and four functions:

FunctionMethodAuth
apiGetGET✅ With token
apiGetWithoutTokenGET❌ Public
apiPostPOST✅ With token
apiPostWithoutTokenPOST❌ Public

All functions accept an ApiProps object and support built-in retry on failure.

URL Constants

Define all URLs in src/constants/url.ts:

export const URL_PATH = {
	auth: {
		register: `api/auth/register`,
	},
	user: {
		profile: `api/user/profile`,
	},
};

The BASE_URL is pulled automatically from Config.API_URL via react-native-config.

Usage with TanStack Query

Mutation (POST without token — e.g. register/login):

import { apiPostWithoutToken } from "@/api";
import { URL_PATH } from "@/constants/url";

const { mutate: submitRegister } = useMutation<RegisterResponse, ApiError>({
	mutationFn: async () => {
		const data = await apiPostWithoutToken({
			url: URL_PATH.auth.register,
			body: { name, email, password },
			tags: "registerAuth", // for logging
		});
		return data?.data;
	},
	onSuccess: (data) => {
		/* handle success */
	},
	onError: (data) => {
		/* handle error */
	},
});

Query (GET with token — e.g. fetch profile):

import { apiGet } from "@/api";
import { URL_PATH } from "@/constants/url";

const { data, isLoading } = useQuery({
	queryKey: ["profile"],
	queryFn: () => apiGet({ url: URL_PATH.user.profile }),
});

With retry on failure:

apiGet({ url: URL_PATH.user.profile, retry: 2 }); // retries up to 2 times

Storage

RNJet uses react-native-mmkv — fast, synchronous, encrypted key-value storage. The instance is pre-configured with AES-256 encryption in src/constants/storage.ts.

Storage Functions

FunctionDescription
handlerGetItem(key)Get a string value
handlerGetAndParseJSON<T>(key)Get and parse a JSON value
handlerSetItem(key, value)Set a string value
handlerRemoveItem(key)Remove a single key
handlerClearItem()Clear all storage

Keys

All storage keys are defined in src/constants/keys.ts — never use raw strings directly:

// src/constants/keys.ts
export const Keys = {
	authToken: "auth_token",
	userProfile: "user_profile",
};

Usage

import {
	handlerSetItem,
	handlerGetItem,
	handlerGetAndParseJSON,
	handlerRemoveItem,
} from "@/constants/storage";
import { Keys } from "@/constants/keys";

// Set
await handlerSetItem(Keys.authToken, token);

// Get string
const token = handlerGetItem(Keys.authToken);

// Get parsed object
const profile = handlerGetAndParseJSON<UserProfile>(Keys.userProfile);

// Remove
await handlerRemoveItem(Keys.authToken);

Internationalization

RNJet uses i18next with a typed wrapper hook useTypedTranslation — so translation keys are fully autocompleted and type-safe. Ships with English (en) and Indonesian (id) out of the box.

Usage

const { translate, i18n } = useTypedTranslation();

// Translate a key
translate("welcome:greeting");

// Switch language
i18n.changeLanguage("id"); // or 'en'

In JSX

Two ways to render a translation:

// Via Text component's trans prop (shorthand)
<Text trans="welcome:greeting" />

// Via translate function
<Text>{translate('welcome:greeting')}</Text>

Toggle Language

const toggleLanguage = () => {
	const nextLang = i18n.language === "en" ? "id" : "en";
	i18n.changeLanguage(nextLang);
};

Adding a New Translation Key

Translation keys follow the namespace:key pattern. Add to both locale files:

// src/i18n/locales/en.json
{ "welcome": { "greeting": "Welcome to RNJet" } }

// src/i18n/locales/id.json
{ "welcome": { "greeting": "Selamat datang di RNJet" } }

Then use it as welcome:greeting.


Environment

react-native-config

Loads environment variables from .env.development and .env.production at build time. Fully integrated with Android flavors and iOS schemes so the right config is always used.

import Config from "react-native-config";

const baseURL = Config.API_URL;

New Architecture Support

All packages in RNJet's stack are compatible with React Native's New Architecture:

FeatureStatus
Fabric (new renderer)✅ Supported
JSI (JS Interface)✅ Supported
Bridgeless mode✅ Compatible
TurboModules✅ Compatible

On this page