Code review guidelines for human teammates and AI agents. All categories are grounded in patterns found in this codebase.
- New hooks must use
createMutationorcreateQueryfromreact-query-kit— never rawuseMutation/useQueryfrom TanStack directly. - Every new query domain must be registered in
src/api/query-factory.tsusingcreateQueryKeys. Query keys must not be hand-rolled strings. - Requests that send a body should wrap the payload as
{ user: <variables> }(established insrc/api/auth/use-login.tsandsrc/api/auth/use-sign-up.ts). Flag any deviation from this pattern and confirm with the team whether it's intentional for that endpoint. - Paginated endpoints must type their response as
PaginateQuery<T>(seesrc/api/types.ts). Infinite list consumers must usenormalizePages()fromsrc/api/common/utils.tsxto flatten pages before passing data to FlashList. - Never add manual camelCase↔snake_case conversion in hooks or components — the interceptors in
src/api/common/interceptors.tshandle this automatically.
- Auth state and token management must not live in screen components. Business logic belongs in
src/components/providers/auth.tsx; routing guards belong insrc/app/_layout.tsx. - Tokens must be stored exclusively via
storeTokens()and read viagetTokenDetails()fromsrc/components/providers/auth.tsx. Never store auth tokens in plain React state,AsyncStorage, or the generalstorageinstance fromsrc/lib/storage.tsx. - All routes that require a logged-in user must be inside the
(app)/group, wrapped inStack.Protectedwithguard={isAuthenticated}insrc/app/_layout.tsx. Never replicate this guard inside individual screen files. - Verify that API calls are not triggered before
ready === truefromAuthProvider. Untilreadyis true, the auth state is not yet hydrated from MMKV. - Token expiry must be evaluated using
dayjscomparison against the ISO string stored inauthStorage— do not compare raw Unix timestamps or useDatedirectly.
- All API hook
VariablesandResponsetypes must be explicitly defined in the same file as the hook. Noany, nounknownwithout a type guard. - Zod validation schemas must drive TypeScript types via
z.infer<typeof schema>— never define the type separately and then write a matching schema; the schema is the source of truth. - Environment variables must be accessed through
Envfrom@/lib/env. Never useprocess.envorConstants.expoConfig?.extradirectly in feature code. - Axios response/request types must use the types from
src/api/common/axios.d.tsaugmentation where applicable.
- No hardcoded English (or any language) strings in JSX. Every user-facing string must be retrieved with
useTranslation(). - Every new key added in a component must have a corresponding entry in
src/translations/en.json. Runpnpm lint:translationsto verify. - Avoid constructing translation keys dynamically (e.g.,
t(`error.${code}`)) unless the full key set is exhaustively defined in the translations file.
- Use NativeWind Tailwind utility classes on React Native primitives. No
StyleSheet.createexcept for top-level layout containers (e.g.,flex: 1wrappers) where Tailwind classes are insufficient. - Components with multiple visual variants must use
tailwind-variants— avoid conditional className string concatenation with ternaries or template literals. - Do not mix NativeWind class-based styling with inline
styleprops on the same element unless there is no Tailwind equivalent.
- Every new component in
src/components/must have a corresponding test file in__tests__/components/. - Always import
render,screen,fireEvent, andcleanupfrom@/lib/test-utils(re-exports fromsrc/lib/test-utils.tsx) — never import them directly from@testing-library/react-native. The customrenderwraps components with required providers. - Form component tests must cover at minimum: (1) renders correctly, (2) shows required-field errors on empty submit, (3) shows format validation error on invalid input. See
__tests__/components/login-form.test.tsxas the canonical reference. - Native module mocks live in
__mocks__/. Before adding a new mock, check existing ones (e.g.,react-native-mmkv,react-native-gesture-handler,moti) to avoid duplication or conflicts. - Use
testIDprops for interactive elements queried withgetByTestId. Follow the naming pattern in existing components (e.g.,login-button,email-input,password-input).
- Use
@shopify/flash-listfor any list that can grow (feeds, search results, infinite scroll). Never useFlatListfor these cases. - Do not call API query hooks inside top-level layout components (
_layout.tsx) if only one child screen actually needs that data. Fetch at the screen level. - Side effects triggered by user actions (form submit, button press) must go inside mutations — not in
useEffectwatching state changes.useEffectis for synchronising with external systems, not for reacting to user interactions. - Avoid anonymous functions or inline object literals as props to FlashList's
renderItemandkeyExtractor— use stable references withuseCallback.