Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 12 additions & 6 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<dt><a href="#disconnect">disconnect(connectionID, [keyToRemoveFromEvictionBlocklist])</a></dt>
<dd><p>Remove the listener for a react component</p>
</dd>
<dt><a href="#notifySubscribersOnNextTick">notifySubscribersOnNextTick(key, value)</a></dt>
<dt><a href="#notifySubscribersOnNextTick">notifySubscribersOnNextTick(key, value, [canUpdateSubscriber])</a></dt>
<dd><p>This method mostly exists for historical reasons as this library was initially designed without a memory cache and one was added later.
For this reason, Onyx works more similar to what you might expect from a native AsyncStorage with reads, writes, etc all becoming
available async. Since we have code in our main applications that might expect things to work this way it&#39;s not safe to change this
Expand Down Expand Up @@ -115,19 +115,24 @@ Onyx.disconnect(connectionID);
```
<a name="notifySubscribersOnNextTick"></a>

## notifySubscribersOnNextTick(key, value)
## notifySubscribersOnNextTick(key, value, [canUpdateSubscriber])
This method mostly exists for historical reasons as this library was initially designed without a memory cache and one was added later.
For this reason, Onyx works more similar to what you might expect from a native AsyncStorage with reads, writes, etc all becoming
available async. Since we have code in our main applications that might expect things to work this way it's not safe to change this
behavior just yet.

**Kind**: global function

| Param | Type |
| --- | --- |
| key | <code>String</code> |
| value | <code>\*</code> |
| Param | Type | Description |
| --- | --- | --- |
| key | <code>String</code> | |
| value | <code>\*</code> | |
| [canUpdateSubscriber] | <code>function</code> | only subscribers that pass this truth test will be updated |
Comment thread
marcaaron marked this conversation as resolved.

**Example**
```js
notifySubscribersOnNextTick(key, value, subscriber => subscriber.initWithStoredValues === false)
```
<a name="set"></a>

## set(key, value) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -252,6 +257,7 @@ Initialize the store with actions and listening for storage events
| [options.captureMetrics] | <code>Boolean</code> | | Enables Onyx benchmarking and exposes the get/print/reset functions |
| [options.shouldSyncMultipleInstances] | <code>Boolean</code> | | Auto synchronize storage events between multiple instances of Onyx running in different tabs/windows. Defaults to true for platforms that support local storage (web/desktop) |
| [option.keysToDisableSyncEvents] | <code>Array.&lt;String&gt;</code> | <code>[]</code> | Contains keys for which we want to disable sync event across tabs. |
| [options.debugSetState] | <code>Boolean</code> | | Enables debugging setState() calls to connected components. |

**Example**
```js
Expand Down
24 changes: 20 additions & 4 deletions lib/Onyx.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,15 @@ function keysChanged(collectionKey, partialCollection) {
/**
* When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks
*
* @example
* keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
*
* @private
* @param {String} key
* @param {*} data
* @param {Function} [canUpdateSubscriber] only subscribers that pass this truth test will be updated
*/
function keyChanged(key, data) {
function keyChanged(key, data, canUpdateSubscriber) {
// Add or remove this key from the recentlyAccessedKeys lists
if (!_.isNull(data)) {
addLastAccessedKey(key);
Expand All @@ -385,7 +389,7 @@ function keyChanged(key, data) {
const stateMappingKeys = _.keys(callbackToStateMapping);
for (let i = stateMappingKeys.length; i--;) {
const subscriber = callbackToStateMapping[stateMappingKeys[i]];
if (!subscriber || !isKeyMatch(subscriber.key, key)) {
if (!subscriber || !isKeyMatch(subscriber.key, key) || (_.isFunction(canUpdateSubscriber) && !canUpdateSubscriber(subscriber))) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB: Is this new logic the same thing as using _.result(subscriber, 'canUpdateSubscriber')? https://underscorejs.org/#result

Maybe not... I think I am thinking of this: https://github.com/Expensify/expensify-common/blob/main/lib/Func.jsx#L13-L19 which would be cool if we could just import it without any additional work :D I don't think that's possible though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_.result() takes a property name as it's second arg so it's a little different

What you are asking sounds similar to Str.result() and I think would work if we did:

Str.result(canUpdateSubscriber, subscriber)

and would return undefined if the first argument was undefined

continue;
}

Expand Down Expand Up @@ -631,12 +635,16 @@ function disconnect(connectionID, keyToRemoveFromEvictionBlocklist) {
* available async. Since we have code in our main applications that might expect things to work this way it's not safe to change this
* behavior just yet.
*
* @example
* notifySubscribersOnNextTick(key, value, subscriber => subscriber.initWithStoredValues === false)
*
* @param {String} key
* @param {*} value
* @param {Function} [canUpdateSubscriber] only subscribers that pass this truth test will be updated
*/
// eslint-disable-next-line rulesdir/no-negated-variables
function notifySubscribersOnNextTick(key, value) {
Promise.resolve().then(() => keyChanged(key, value));
function notifySubscribersOnNextTick(key, value, canUpdateSubscriber) {
Promise.resolve().then(() => keyChanged(key, value, canUpdateSubscriber));
}

/**
Expand Down Expand Up @@ -703,6 +711,14 @@ function set(key, value) {
Logger.logAlert(`Onyx.set() called after Onyx.merge() for key: ${key}. It is recommended to use set() or merge() not both.`);
}

// If the value in the cache is the same as what we have then do not update subscribers unless they
// have initWithStoredValues: false then they MUST get all updates even if nothing has changed.
if (!cache.hasValueChanged(key, value)) {
cache.addToAccessedKeys(key);
notifySubscribersOnNextTick(key, value, subscriber => subscriber.initWithStoredValues === false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
notifySubscribersOnNextTick(key, value, subscriber => subscriber.initWithStoredValues === false);
notifySubscribersOnNextTick(key, value, !subscriber.initWithStoredValues);

Why does the third argument need to be a function? Can it be a simple boolean instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't have to be a function. I had it as a simple boolean, but feel the function is more intuitive and also puts the context for why we are doing what we are doing where we are doing it. Check this commit to get a feel for what I mean:

8c7b7d9

return Promise.resolve();
}

// Adds the key to cache when it's not available
cache.set(key, value);
notifySubscribersOnNextTick(key, value);
Expand Down
10 changes: 10 additions & 0 deletions lib/OnyxCache.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _ from 'underscore';
import {deepEqual} from 'fast-equals';
import fastMerge from './fastMerge';

const isDefined = _.negate(_.isUndefined);
Expand Down Expand Up @@ -191,6 +192,15 @@ class OnyxCache {
setRecentKeysLimit(limit) {
this.maxRecentKeysSize = limit;
}

/**
* @param {String} key
* @param {*} value
* @returns {Boolean}
*/
hasValueChanged(key, value) {
return !deepEqual(this.storageMap[key], value);
}
}

const instance = new OnyxCache();
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
},
"dependencies": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
"lodash": "^4.17.21",
"underscore": "^1.13.1"
},
Expand Down
Loading