Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement useOnyx hook #462

Merged
merged 56 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
4c1624b
First test implementation of useOnyx hook
fabioh8010 Jan 25, 2024
9c10b25
Fix isSafeEvictionKey condition
fabioh8010 Jan 25, 2024
be3204b
Fix some ESLint rules
fabioh8010 Jan 25, 2024
f0a7c56
Fix key logic to allow changes
fabioh8010 Jan 25, 2024
615e39c
Fix value returned by hook when it is a collection
fabioh8010 Jan 25, 2024
1588c8a
Initial implementation of useSyncExternalStore hook version
fabioh8010 Jan 25, 2024
d829575
Merge tag 'v2.0.3' into feature/useOnyx
fabioh8010 Feb 7, 2024
1436535
Improve getSnapshot logic to fix cache issue when changing the key
fabioh8010 Feb 7, 2024
4c0f445
Fix build:watch script to consider TS files
fabioh8010 Feb 13, 2024
572079f
Fix ESlint and TSConfig
fabioh8010 Feb 13, 2024
6b2ae81
Extract usePrevious to a separate file
fabioh8010 Feb 13, 2024
8c8eb38
Use cache from OnyxCache
fabioh8010 Feb 13, 2024
b028d23
Add condition to ensure only collection member keys can be changed
fabioh8010 Feb 15, 2024
9716d4b
Fix getSnapshot() logic and improve typings
fabioh8010 Feb 15, 2024
b5c208c
Implement initialValue logic
fabioh8010 Feb 16, 2024
7731c73
Added comment and minor adjustments
fabioh8010 Feb 16, 2024
cedee66
Implement fetch status logic and make useOnyx return a object
fabioh8010 Feb 16, 2024
49fabbd
Remove old useOnyx from code
fabioh8010 Feb 16, 2024
9a7ff48
Fix selector types
fabioh8010 Feb 16, 2024
c8be6d1
Improve isCollectionKey type
fabioh8010 Feb 16, 2024
26e2451
Merge branch 'main' into feature/useOnyx
fabioh8010 Feb 16, 2024
e16e26c
Merge remote-tracking branch 'origin/main' into feature/useOnyx
fabioh8010 Feb 16, 2024
ee16e80
Format code
fabioh8010 Feb 16, 2024
ea61b54
Update eslint-config-expensify
fabioh8010 Feb 16, 2024
7a0da52
Improve key changing check
fabioh8010 Feb 16, 2024
c43d4cb
Improve comments
fabioh8010 Feb 16, 2024
461a29c
Change fetch status to loaded after onStoreChange
fabioh8010 Feb 16, 2024
c349027
Remove hook plugin
fabioh8010 Feb 16, 2024
3c7a14d
Rename previousDataRef
fabioh8010 Feb 16, 2024
e00b2b2
Fix useOnyx return value type when key is collection key and selector…
fabioh8010 Feb 16, 2024
8a71c90
Remove eslint-plugin-react-hooks package
fabioh8010 Feb 16, 2024
4fa5ab7
Fix lint errors
fabioh8010 Feb 16, 2024
d10dc64
Simplify getSnapshot() logic
fabioh8010 Feb 16, 2024
ff6f850
Improve typings
fabioh8010 Feb 16, 2024
311160f
Improve key changing check logic
fabioh8010 Feb 21, 2024
c0a4fc4
Adjust getSnapshot() comments
fabioh8010 Feb 21, 2024
5086b95
Stabilizes selector reference to avoid unnecessary calls to getSnapsh…
fabioh8010 Feb 21, 2024
6fec631
Fix comment
fabioh8010 Feb 21, 2024
7d177b1
First implementation of allowStaleData
fabioh8010 Feb 23, 2024
9df252f
Address review comments
fabioh8010 Feb 23, 2024
1f07efd
Merge tag 'v2.0.6' into feature/useOnyx
fabioh8010 Feb 27, 2024
aaa1397
Change return format
fabioh8010 Feb 27, 2024
1f62a8f
Re-write implementation to fix loading states
fabioh8010 Mar 5, 2024
4b3beea
Merge tag 'v2.0.10' into feature/useOnyx
fabioh8010 Mar 5, 2024
9d97321
Implement initial tests for useOnyx
fabioh8010 Mar 6, 2024
f107c93
Address review comments
fabioh8010 Mar 7, 2024
d74f7b5
Add tests for stale data
fabioh8010 Mar 7, 2024
4927f38
Implement tests for initWithStoredValues and fix this logic in the hook
fabioh8010 Mar 7, 2024
96da9f0
Add another test for selector
fabioh8010 Mar 7, 2024
08ba557
Implement additional test for stale data
fabioh8010 Mar 8, 2024
a25b50f
Improve CachedValue type
fabioh8010 Mar 8, 2024
daad846
Improve initialValue type
fabioh8010 Mar 8, 2024
fd00b7c
Export FetchStatus type
fabioh8010 Mar 8, 2024
7d0096b
Merge remote-tracking branch 'origin/main' into feature/useOnyx
fabioh8010 Mar 8, 2024
555e6f1
Minor fix
fabioh8010 Mar 8, 2024
3c25c3b
Update docs
fabioh8010 Mar 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ module.exports = {
tsx: 'never',
},
],
'rulesdir/prefer-onyx-connect-in-libs': 'off',
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
},
},
{
Expand Down
61 changes: 34 additions & 27 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,15 @@ cause react to schedule the updates at once instead of after each other. This is
and runs it through a reducer function to return a subset of the data according to a selector.
The resulting collection will only contain items that are returned by the selector.</p>
</dd>
<dt><a href="#isCollectionKey">isCollectionKey(key)</a> ⇒ <code>Boolean</code></dt>
<dd><p>Checks to see if the a subscriber&#39;s supplied key
is associated with a collection of keys.</p>
</dd>
<dt><a href="#isCollectionMemberKey">isCollectionMemberKey(collectionKey, key)</a> ⇒ <code>Boolean</code></dt>
<dd></dd>
<dt><a href="#splitCollectionMemberKey">splitCollectionMemberKey(key)</a> ⇒ <code>Array.&lt;String&gt;</code></dt>
<dd><p>Splits a collection member key into the collection key part and the ID part.</p>
</dd>
<dt><a href="#tryGetCachedValue">tryGetCachedValue(key, mapping)</a> ⇒ <code>Mixed</code></dt>
<dd><p>Tries to get a value from the cache. If the value is not present in cache it will return the default value or undefined.
If the requested key is a collection, it will return an object with all the collection members.</p>
Expand All @@ -34,7 +41,7 @@ If the requested key is a collection, it will return an object with all the coll
<dt><a href="#disconnect">disconnect(connectionID, [keyToRemoveFromEvictionBlocklist])</a></dt>
<dd><p>Remove the listener for a react component</p>
</dd>
<dt><a href="#scheduleSubscriberUpdate">scheduleSubscriberUpdate(key, value, [canUpdateSubscriber])</a> ⇒ <code>Promise</code></dt>
<dt><a href="#scheduleSubscriberUpdate">scheduleSubscriberUpdate(key, value, prevValue, [canUpdateSubscriber])</a> ⇒ <code>Promise</code></dt>
<dd><p>Schedules an update that will be appended to the macro task queue (so it doesn&#39;t update the subscribers immediately).</p>
</dd>
<dt><a href="#scheduleNotifyCollectionSubscribers">scheduleNotifyCollectionSubscribers(key, value)</a> ⇒ <code>Promise</code></dt>
Expand Down Expand Up @@ -90,13 +97,6 @@ value will be saved to storage after the default value.</p>
<dt><a href="#setMemoryOnlyKeys">setMemoryOnlyKeys(keyList)</a></dt>
<dd><p>When set these keys will not be persisted to storage</p>
</dd>
<dt><a href="#onClear">onClear(callback)</a></dt>
<dd><p>Sets the callback to be called when the clear finishes executing.</p>
</dd>
<dt><a href="#subscribeToEvents">subscribeToEvents()</a></dt>
<dd><p>Subscribes to the Broadcast channel and executes actions based on the
types of events.</p>
</dd>
<dt><a href="#init">init([options])</a></dt>
<dd><p>Initialize the store with actions and listening for storage events</p>
</dd>
Expand Down Expand Up @@ -153,6 +153,18 @@ The resulting collection will only contain items that are returned by the select
| selector | <code>String</code> \| <code>function</code> | (see method docs for getSubsetOfData() for full details) |
| [withOnyxInstanceState] | <code>Object</code> | |

<a name="isCollectionKey"></a>

## isCollectionKey(key) ⇒ <code>Boolean</code>
Checks to see if the a subscriber's supplied key
is associated with a collection of keys.

**Kind**: global function

| Param | Type |
| --- | --- |
| key | <code>String</code> |

<a name="isCollectionMemberKey"></a>

## isCollectionMemberKey(collectionKey, key) ⇒ <code>Boolean</code>
Expand All @@ -163,6 +175,18 @@ The resulting collection will only contain items that are returned by the select
| collectionKey | <code>String</code> |
| key | <code>String</code> |

<a name="splitCollectionMemberKey"></a>

## splitCollectionMemberKey(key) ⇒ <code>Array.&lt;String&gt;</code>
Splits a collection member key into the collection key part and the ID part.

**Kind**: global function
**Returns**: <code>Array.&lt;String&gt;</code> - A tuple where the first element is the collection part and the second element is the ID part.

| Param | Type | Description |
| --- | --- | --- |
| key | <code>String</code> | The collection member key to split. |

<a name="tryGetCachedValue"></a>

## tryGetCachedValue(key, mapping) ⇒ <code>Mixed</code>
Expand Down Expand Up @@ -221,7 +245,7 @@ Onyx.disconnect(connectionID);
```
<a name="scheduleSubscriberUpdate"></a>

## scheduleSubscriberUpdate(key, value, [canUpdateSubscriber]) ⇒ <code>Promise</code>
## scheduleSubscriberUpdate(key, value, prevValue, [canUpdateSubscriber]) ⇒ <code>Promise</code>
Schedules an update that will be appended to the macro task queue (so it doesn't update the subscribers immediately).

**Kind**: global function
Expand All @@ -230,6 +254,7 @@ Schedules an update that will be appended to the macro task queue (so it doesn't
| --- | --- | --- |
| key | <code>String</code> | |
| value | <code>\*</code> | |
| prevValue | <code>\*</code> | |
| [canUpdateSubscriber] | <code>function</code> | only subscribers that pass this truth test will be updated |

**Example**
Expand Down Expand Up @@ -410,24 +435,6 @@ When set these keys will not be persisted to storage
| --- | --- |
| keyList | <code>Array.&lt;string&gt;</code> |

<a name="onClear"></a>

## onClear(callback)
Sets the callback to be called when the clear finishes executing.

**Kind**: global function

| Param | Type |
| --- | --- |
| callback | <code>function</code> |

<a name="subscribeToEvents"></a>

## subscribeToEvents()
Subscribes to the Broadcast channel and executes actions based on the
types of events.

**Kind**: global function
<a name="init"></a>

## init([options])
Expand Down
39 changes: 36 additions & 3 deletions lib/Onyx.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Component} from 'react';
import * as Logger from './Logger';
import {CollectionKey, CollectionKeyBase, DeepRecord, KeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey} from './types';
import {CollectionKey, CollectionKeyBase, DeepRecord, KeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, Selector} from './types';

/**
* Represents a mapping object where each `OnyxKey` maps to either a value of its corresponding type in `KeyValueMapping` or `null`.
Expand All @@ -20,6 +20,11 @@ type BaseConnectOptions = {
initWithStoredValues?: boolean;
};

type TryGetCachedValueMapping<TKey extends OnyxKey> = {
selector?: Selector<TKey, unknown, unknown>;
withOnyxInstance?: Component;
};

/**
* Represents the options used in `Onyx.connect()` method.
* The type is built from `BaseConnectOptions` and extended to handle key/callback related options.
Expand All @@ -35,7 +40,7 @@ type BaseConnectOptions = {
type ConnectOptions<TKey extends OnyxKey> = BaseConnectOptions &
(
| {
key: TKey extends CollectionKey ? TKey : never;
key: TKey extends CollectionKeyBase ? TKey : never;
callback?: (value: OnyxCollection<KeyValueMapping[TKey]>) => void;
waitForCollectionCallback: true;
}
Expand Down Expand Up @@ -114,6 +119,21 @@ declare const METHOD: {
*/
declare function getAllKeys(): Promise<Array<OnyxKey>>;

/**
* Checks to see if the a subscriber's supplied key
* is associated with a collection of keys.
*/
declare function isCollectionKey(key: OnyxKey): key is CollectionKeyBase;

declare function isCollectionMemberKey<TCollectionKey extends CollectionKeyBase>(collectionKey: TCollectionKey, key: string): key is `${TCollectionKey}${string}`;

/**
* Splits a collection member key into the collection key part and the ID part.
* @param key - The collection member key to split.
* @returns A tuple where the first element is the collection part and the second element is the ID part.
*/
declare function splitCollectionMemberKey<TKey extends CollectionKey>(key: TKey): [TKey extends `${infer Prefix}_${string}` ? `${Prefix}_` : never, string];
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Checks to see if this key has been flagged as
* safe for removal.
Expand Down Expand Up @@ -289,6 +309,15 @@ declare function hasPendingMergeForKey(key: OnyxKey): boolean;
*/
declare function setMemoryOnlyKeys(keyList: OnyxKey[]): void;

/**
* Tries to get a value from the cache. If the value is not present in cache it will return the default value or undefined.
* If the requested key is a collection, it will return an object with all the collection members.
*/
declare function tryGetCachedValue<TKey extends OnyxKey>(
key: TKey,
mapping?: TryGetCachedValueMapping,
): TKey extends CollectionKeyBase ? OnyxCollection<KeyValueMapping[TKey]> | undefined : OnyxEntry<KeyValueMapping[TKey]> | undefined;

declare const Onyx: {
connect: typeof connect;
disconnect: typeof disconnect;
Expand All @@ -307,7 +336,11 @@ declare const Onyx: {
isSafeEvictionKey: typeof isSafeEvictionKey;
METHOD: typeof METHOD;
setMemoryOnlyKeys: typeof setMemoryOnlyKeys;
tryGetCachedValue: typeof tryGetCachedValue;
isCollectionKey: typeof isCollectionKey;
isCollectionMemberKey: typeof isCollectionMemberKey;
splitCollectionMemberKey: typeof splitCollectionMemberKey;
};

export default Onyx;
export {OnyxUpdate, ConnectOptions};
export {ConnectOptions, OnyxUpdate};
19 changes: 18 additions & 1 deletion lib/Onyx.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ function getAllKeys() {
* Checks to see if the a subscriber's supplied key
* is associated with a collection of keys.
*
* @private
* @param {String} key
* @returns {Boolean}
*/
Expand All @@ -213,6 +212,21 @@ function isCollectionMemberKey(collectionKey, key) {
return Str.startsWith(key, collectionKey) && key.length > collectionKey.length;
}

/**
* Splits a collection member key into the collection key part and the ID part.
* @param {String} key - The collection member key to split.
* @returns {Array<String>} A tuple where the first element is the collection part and the second element is the ID part.
*/
function splitCollectionMemberKey(key) {
const underscoreIndex = key.indexOf('_');

if (underscoreIndex === -1) {
throw new Error(`Invalid ${key} key provided, only collection keys are allowed.`);
}

return [key.substring(0, underscoreIndex + 1), key.substring(underscoreIndex + 1)];
}

/**
* Checks to see if a provided key is the exact configured key of our connected subscriber
* or if the provided key is a collection member key (in case our configured key is a "collection key")
Expand Down Expand Up @@ -1670,6 +1684,9 @@ const Onyx = {
setMemoryOnlyKeys,
tryGetCachedValue,
hasPendingMergeForKey,
isCollectionKey,
isCollectionMemberKey,
splitCollectionMemberKey,
};

export default Onyx;
21 changes: 19 additions & 2 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
import Onyx, {OnyxUpdate, ConnectOptions} from './Onyx';
import {CustomTypeOptions, OnyxCollection, OnyxEntry, NullishDeep, KeyValueMapping, OnyxKey, Selector, WithOnyxInstanceState} from './types';
import {CustomTypeOptions, OnyxCollection, OnyxEntry, NullishDeep, KeyValueMapping, OnyxKey, Selector, WithOnyxInstanceState, OnyxValue} from './types';
import withOnyx from './withOnyx';
import useOnyx, {UseOnyxResult, FetchStatus} from './useOnyx';

export default Onyx;
export {CustomTypeOptions, OnyxCollection, OnyxEntry, OnyxUpdate, withOnyx, ConnectOptions, NullishDeep, KeyValueMapping, OnyxKey, Selector, WithOnyxInstanceState};
export {
CustomTypeOptions,
OnyxCollection,
OnyxEntry,
OnyxUpdate,
withOnyx,
ConnectOptions,
NullishDeep,
KeyValueMapping,
OnyxKey,
Selector,
WithOnyxInstanceState,
useOnyx,
UseOnyxResult,
OnyxValue,
FetchStatus,
};
3 changes: 2 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Onyx from './Onyx';
import withOnyx from './withOnyx';
import useOnyx from './useOnyx';

export default Onyx;
export {withOnyx};
export {withOnyx, useOnyx};
11 changes: 9 additions & 2 deletions lib/storage/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,20 @@ const idbKeyvalMock: StorageProvider = {
},
multiSet(pairs) {
const setPromises = pairs.map(([key, value]) => this.setItem(key, value));
return new Promise((resolve) => Promise.all(setPromises).then(() => resolve(storageMapInternal)));
return new Promise((resolve) => {
Promise.all(setPromises).then(() => resolve(storageMapInternal));
});
},
getItem(key) {
return Promise.resolve(storageMapInternal[key]);
},
multiGet(keys) {
const getPromises = keys.map((key) => new Promise((resolve) => this.getItem(key).then((value) => resolve([key, value]))));
const getPromises = keys.map(
(key) =>
new Promise((resolve) => {
this.getItem(key).then((value) => resolve([key, value]));
}),
);
return Promise.all(getPromises) as Promise<KeyValuePairList>;
},
multiMerge(pairs) {
Expand Down
6 changes: 6 additions & 0 deletions lib/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ type CollectionKey = `${CollectionKeyBase}${string}`;
*/
type OnyxKey = Key | CollectionKey;

/**
* Represents a Onyx value that can be either a single entry or a collection of entries, depending on the `TKey` provided.
*/
type OnyxValue<TKey extends OnyxKey> = TKey extends CollectionKeyBase ? OnyxCollection<KeyValueMapping[TKey]> : OnyxEntry<KeyValueMapping[TKey]>;

/**
* Represents a mapping of Onyx keys to values, where keys are either normal or collection Onyx keys
* and values are the corresponding values in Onyx's state.
Expand Down Expand Up @@ -239,6 +244,7 @@ export {
OnyxCollection,
OnyxEntry,
OnyxKey,
OnyxValue,
Selector,
NullishDeep,
WithOnyxInstanceState,
Expand Down
14 changes: 14 additions & 0 deletions lib/useLiveRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {useRef} from 'react';

/**
* Creates a mutable reference to a value, useful when you need to
* maintain a reference to a value that may change over time without triggering re-renders.
*/
function useLiveRef<T>(value: T) {
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
const ref = useRef<T>(value);
ref.current = value;

return ref;
}

export default useLiveRef;
Loading
Loading