Skip to content

Commit 4b5fcff

Browse files
Merge branch 'Expensify:main' into refactor-onyx-20
2 parents 833d2d1 + 198d552 commit 4b5fcff

11 files changed

Lines changed: 111 additions & 21 deletions

File tree

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* [canBeMissing onyx param](#canbemissing-onyx-param)
2525

2626
#### Additional Reading
27+
* [Application Philosophy](contributingGuides/philosophies/INDEX.md)
2728
* [API Details](contributingGuides/API.md)
2829
* [Offline First](contributingGuides/OFFLINE_UX.md)
2930
* [Contributing to Expensify](contributingGuides/CONTRIBUTING.md)
@@ -525,7 +526,7 @@ You can only build HybridApp if you have been granted access to [`Mobile-Expensi
525526
4. Run `git config --global submodule.recurse true` in order to have the submodule updated when you pull App.
526527

527528

528-
> [!Note]
529+
> [!Note]
529530
> #### For external agencies and C+ contributors only
530531
>
531532
> If you'd like to modify the `Mobile-Expensify` source code, it is best that you create your own fork. Then, you can swap origin of the remote repository by executing this command:
@@ -560,15 +561,15 @@ If for some reason, you need to target the standalone NewDot application, you ca
560561

561562
### Working with HybridApp vs Standalone NewDot
562563

563-
Day-to-day work with **HybridApp** shouldn't differ much from working on the standalone **NewDot** repository.
564-
The primary difference is that the native code, which runs React Native, is located in the following directories:
564+
Day-to-day work with **HybridApp** shouldn't differ much from working on the standalone **NewDot** repository.
565+
The primary difference is that the native code, which runs React Native, is located in the following directories:
565566
566567
- `./Mobile-Expensify/Android`
567568
- `./Mobile-Expensify/iOS`
568569
569570
### Important Notes:
570571
1. **Root Folders Do Not Affect HybridApp Builds:**
571-
- Changes made to the `./android` and `./ios` folders at the root of the repository **won't affect the HybridApp build**.
572+
- Changes made to the `./android` and `./ios` folders at the root of the repository **won't affect the HybridApp build**.
572573

573574
2. **Modifying iOS Code for HybridApp:**
574575
- If you need to remove `Pods`, you must do it in the **`./Mobile-Expensify/iOS`** directory.
@@ -583,23 +584,23 @@ The primary difference is that the native code, which runs React Native, is loca
583584
584585
### Updating the `Mobile-Expensify` Submodule
585586
586-
The `Mobile-Expensify` directory is a **Git submodule**. This means it points to a specific commit on the `Mobile-Expensify` repository.
587+
The `Mobile-Expensify` directory is a **Git submodule**. This means it points to a specific commit on the `Mobile-Expensify` repository.
587588
588-
If you'd like to fetch the submodule while executing the `git pull` command in `Expensify/App` instead of updating it manually you can run this command in the root of the project:
589+
If you'd like to fetch the submodule while executing the `git pull` command in `Expensify/App` instead of updating it manually you can run this command in the root of the project:
589590

590591
```
591592
git config submodule.recurse true
592593
```
593594

594-
> [!WARNING]
595+
> [!WARNING]
595596
> Please, remember that the submodule will get updated automatically only after executing the `git pull` command - if you switch between branches it is still recommended to execute `git submodule update` to make sure you're working on a compatible submodule version!
596597

597598
If you'd like to download the most recent changes from the `main` branch, please use the following command:
598599
```bash
599600
git submodule update --remote
600601
```
601602

602-
It's important to emphasize that a git submodule is just a **regular git repository** after all. It means that you can switch branches, pull the newest changes, and execute all regular git commands within the `Mobile-Expensify` directory.
603+
It's important to emphasize that a git submodule is just a **regular git repository** after all. It means that you can switch branches, pull the newest changes, and execute all regular git commands within the `Mobile-Expensify` directory.
603604

604605
### Adding HybridApp-related patches
605606

@@ -805,7 +806,7 @@ Some pointers:
805806
- When working with translations that involve plural forms, it's important to handle different cases correctly.
806807

807808
For example:
808-
- zero: Used when there are no items **(optional)**.
809+
- zero: Used when there are no items **(optional)**.
809810
- one: Used when there's exactly one item.
810811
- two: Used when there's two items. **(optional)**
811812
- few: Used for a small number of items **(optional)**.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Application Philosophies
2+
The following pages include details on how different systems are meant to be used.
3+
4+
## Contents
5+
* [Routing Philosophy](contributingGuides/philosophies/ROUTING.md)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Routing Philosophy
2+
Learn how URLs are constructed and used to route users through all parts of the application.
3+
4+
[React Navigation](https://reactnavigation.org/) is the library used for all routing and navigation, so refer to its [docs](https://reactnavigation.org/docs/getting-started) to understand how the library works. This page will not focus on how routing works, but how URLs are constructed and what patterns to follow when adding new URLs.
5+
6+
## Definitions
7+
- URL - The web address for every page in the app. On web, this is shown on the browser bar, and is of the format `https://new.expensify.com/<route>`
8+
- Route - A unique identifier of a specific location within the app's navigation hierarchy
9+
- Object Route - A route pointing to a specific object (eg. report, transaction, workspace, etc.)
10+
- Page Route - A route for a specific page (eg. settings, add bank account, etc.)
11+
12+
This guide is based on [[PSR-1]] and [[PSR-2]].
13+
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and
14+
"OPTIONAL" in this document are to be interpreted as described in [[RFC 2119]].
15+
16+
## Rules
17+
When adding new routes, or refactoring existing routes, follow these rules:
18+
19+
### - MUST be unique
20+
There are no aliases where different routes lead to the same place.
21+
22+
### - SHOULD be short
23+
Aim for the shortest possible URL that is also still human readable.
24+
25+
Exceptions:
26+
- When abbreviated paths are used in specific instances like `r/` (for reports) and `a/` (for accounts) for strategic purposes
27+
28+
### - MUST use kebab-case for all parts of a URL (eg. words separated by hyphens)
29+
### - MUST use all lower case words
30+
### - MUST be defined in `ROUTES.ts`
31+
### - MUST convey page hierarchy
32+
Just like breadcrumbs, the URL should reflect the path a user has taken.
33+
34+
Example of the paths taken to manage a workspace member:
35+
1. `workspaces`
36+
1. `workspaces/:policyID/overview`
37+
1. `workspaces/:policyID/members`
38+
1. `workspaces/:policyID/members/:memberID`
39+
40+
### - MUST have the route parameters validated
41+
Since users can edit the route parameters at any time, to any value, the parameters must always be validated to ensure they are correct and the user has access.
42+
43+
### - MUST NOT start with a slash
44+
### - MUST NOT contain jargon or acronyms
45+
### - MUST NOT contain sensitive information that could be logged by third-parties
46+
Internet routers and third-party-services can see and store any information in the URL. Do not put things like passwords, auth tokens, or PII (personally identifiable information).
47+
48+
### - SHOULD NOT use query parameters
49+
Exceptions:
50+
- When a URL needs to be encoded and added to the path (eg. `?backTo=URL`, `?forwardTo=URL`)
51+
- When complex data needs to be part of the path (eg. `/search?q=QUERY`)
52+
53+
### - SHOULD NOT use optional parameters
54+
If there are optional parameters, use two separate route definitions instead to be explicit.
55+
56+
### - SHOULD use plural nouns when there are multiple objects that can exist
57+
Exceptions:
58+
59+
- When abbreviated paths are used in specific instances like `r/` (for reports) and `a/` (for accounts) then plurality does not matter
60+
61+
### - SHOULD use only the minimal necessary information to render the page
62+
Examples:
63+
- `r/:threadReportID` GOOD - `threadReportID` is all that is needed and all the rest of the page can be derived from that
64+
- `r/:parentReportID/:threadReportID` BAD - the `parentReportID` is not necessary so it just adds cruft to the URL
65+
66+
Exceptions:
67+
68+
- When multiple IDs are **required** to render the page.

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@
179179
"react-native-modal": "^13.0.0",
180180
"react-native-nitro-modules": "0.26.2",
181181
"react-native-nitro-sqlite": "9.1.10",
182-
"react-native-onyx": "2.0.127",
182+
"react-native-onyx": "2.0.130",
183183
"react-native-pager-view": "6.5.3",
184184
"react-native-pdf": "6.7.3",
185185
"react-native-performance": "^5.1.4",

src/ROUTES.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* NOTE!!!!
3+
*
4+
* Refer to ./contributingGuides/philosophies/ROUTING.md for information on how to construct routes.
5+
*/
16
import type {TupleToUnion, ValueOf} from 'type-fest';
27
import type {SearchQueryString} from './components/Search/types';
38
import type CONST from './CONST';

src/hooks/useSidebarOrderedReports.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import {deepEqual} from 'fast-equals';
22
import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
3-
import type {OnyxEntry} from 'react-native-onyx';
3+
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
44
import Log from '@libs/Log';
55
import {getPolicyEmployeeListByIdWithoutCurrentUser} from '@libs/PolicyUtils';
6+
import {reportAttributesSelector} from '@libs/ReportUtils';
67
import SidebarUtils from '@libs/SidebarUtils';
78
import CONST from '@src/CONST';
89
import ONYXKEYS from '@src/ONYXKEYS';
@@ -44,6 +45,8 @@ const policySelector = (policy: OnyxEntry<OnyxTypes.Policy>): PartialPolicyForSi
4445
employeeList: policy.employeeList,
4546
}) as PartialPolicyForSidebar;
4647

48+
const policiesSelector = (policies: OnyxCollection<OnyxTypes.Policy>) => mapOnyxCollectionItems(policies, policySelector);
49+
4750
function SidebarOrderedReportsContextProvider({
4851
children,
4952
/**
@@ -59,13 +62,13 @@ function SidebarOrderedReportsContextProvider({
5962
const {localeCompare} = useLocalize();
6063
const [priorityMode = CONST.PRIORITY_MODE.DEFAULT] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE, {canBeMissing: true});
6164
const [chatReports, {sourceValue: reportUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
62-
const [policies, {sourceValue: policiesUpdates}] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: (c) => mapOnyxCollectionItems(c, policySelector), canBeMissing: true});
65+
const [policies, {sourceValue: policiesUpdates}] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: policiesSelector, canBeMissing: true});
6366
const [transactions, {sourceValue: transactionsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true});
6467
const [transactionViolations, {sourceValue: transactionViolationsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
6568
const [reportNameValuePairs, {sourceValue: reportNameValuePairsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {canBeMissing: true});
6669
const [, {sourceValue: reportsDraftsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true});
6770
const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true});
68-
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports, canBeMissing: true});
71+
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: reportAttributesSelector, canBeMissing: true});
6972
const [currentReportsToDisplay, setCurrentReportsToDisplay] = useState<ReportsToDisplayInLHN>({});
7073

7174
const {shouldUseNarrowLayout} = useResponsiveLayout();

src/libs/E2E/actions/waitForAppLoaded.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import ONYXKEYS from '@src/ONYXKEYS';
44
// Once we get the sidebar loaded end mark we know that the app is ready to be used:
55
export default function waitForAppLoaded(): Promise<void> {
66
return new Promise((resolve) => {
7-
const connection = Onyx.connect({
7+
// We have used `connectWithoutView` here because it is not connected to any UI
8+
const connection = Onyx.connectWithoutView({
89
key: ONYXKEYS.IS_SIDEBAR_LOADED,
910
callback: (isSidebarLoaded) => {
1011
if (!isSidebarLoaded) {

src/libs/ReportUtils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11276,6 +11276,10 @@ function getReportStatusTranslation(stateNum?: number, statusNum?: number): stri
1127611276
return '';
1127711277
}
1127811278

11279+
function reportAttributesSelector(reportAttributes: OnyxEntry<ReportAttributesDerivedValue>) {
11280+
return reportAttributes?.reports;
11281+
}
11282+
1127911283
export {
1128011284
areAllRequestsBeingSmartScanned,
1128111285
buildOptimisticAddCommentReportAction,
@@ -11656,6 +11660,7 @@ export {
1165611660
isWorkspaceTaskReport,
1165711661
isWorkspaceThread,
1165811662
getReportStatusTranslation,
11663+
reportAttributesSelector,
1165911664
};
1166011665

1166111666
export type {

src/libs/actions/Device/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ function getDeviceID(): Promise<string | null> {
1616
return;
1717
}
1818

19-
const connection = Onyx.connect({
19+
// Use connectWithoutView because this is a one-time data fetch that disconnects immediately without triggering UI updates
20+
// and this onyx key is created for non-UI task only.
21+
const connection = Onyx.connectWithoutView({
2022
key: ONYXKEYS.DEVICE_ID,
2123
callback: (id) => {
2224
Onyx.disconnect(connection);

0 commit comments

Comments
 (0)