Skip to content

Commit

Permalink
Merge pull request #462 from fabioh8010/feature/useOnyx
Browse files Browse the repository at this point in the history
Implement useOnyx hook
  • Loading branch information
roryabraham authored Mar 8, 2024
2 parents b4d0615 + 3c25c3b commit 0f7938d
Show file tree
Hide file tree
Showing 16 changed files with 1,436 additions and 1,616 deletions.
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',
},
},
{
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];

/**
* 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) {
const ref = useRef<T>(value);
ref.current = value;

return ref;
}

export default useLiveRef;
Loading

0 comments on commit 0f7938d

Please sign in to comment.