-
Notifications
You must be signed in to change notification settings - Fork 74
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
Add API reference. Update Onyx docs to explain current merge() behavior. #106
Merged
Merged
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
8649b92
Update Onyx docs
marcaaron f9b7edb
Add API doc
marcaaron e13ae1f
add more private flags
marcaaron f9b1aaa
Add more detail to docs and examples to API reference
marcaaron cca0351
Clean up JS docs some more
marcaaron f35b108
add import to example
marcaaron 1b484eb
More info about merge
marcaaron 397b62c
fix json
marcaaron 859e242
add tip about memory leaks
marcaaron bbee5d2
clarify the case to use set when resetting some data
marcaaron 96efd54
Fix Function doc. Improve punctuation.
marcaaron File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
<!---These docs were automatically generated. Do not edit them directly run `npm run build-docs` script--> | ||
|
||
# API Reference | ||
|
||
## Functions | ||
|
||
<dl> | ||
<dt><a href="#connect">connect(mapping)</a> ⇒ <code>Number</code></dt> | ||
<dd><p>Subscribes a react component's state directly to a store key</p> | ||
</dd> | ||
<dt><a href="#disconnect">disconnect(connectionID, [keyToRemoveFromEvictionBlocklist])</a></dt> | ||
<dd><p>Remove the listener for a react component</p> | ||
</dd> | ||
<dt><a href="#set">set(key, value)</a> ⇒ <code>Promise</code></dt> | ||
<dd><p>Write a value to our store with the given key</p> | ||
</dd> | ||
<dt><a href="#multiSet">multiSet(data)</a> ⇒ <code>Promise</code></dt> | ||
<dd><p>Sets multiple keys and values</p> | ||
</dd> | ||
<dt><a href="#merge">merge(key, value)</a> ⇒ <code>Promise</code></dt> | ||
<dd><p>Merge a new value into an existing value at a key.</p> | ||
<p>The types of values that can be merged are <code>Object</code> and <code>Array</code>. To set another type of value use <code>Onyx.set()</code>. Merge | ||
behavior uses lodash/merge under the hood for <code>Object</code> and simple concatenation for <code>Array</code>. However, it's important | ||
to note that if you have an array value property on an <code>Object</code> that the default behavior of lodash/merge is not to | ||
concatenate. See here: <a href="https://github.com/lodash/lodash/issues/2872">https://github.com/lodash/lodash/issues/2872</a></p> | ||
<p>Calls to <code>Onyx.merge()</code> are batched so that any calls performed in a single tick will stack in a queue and get | ||
applied in the order they were called. Note: <code>Onyx.set()</code> calls do not work this way so use caution when mixing | ||
<code>Onyx.merge()</code> and <code>Onyx.set()</code>.</p> | ||
</dd> | ||
<dt><a href="#clear">clear()</a> ⇒ <code>Promise.<void></code></dt> | ||
<dd><p>Clear out all the data in the store</p> | ||
</dd> | ||
<dt><a href="#mergeCollection">mergeCollection(collectionKey, collection)</a> ⇒ <code>Promise</code></dt> | ||
<dd><p>Merges a collection based on their keys</p> | ||
</dd> | ||
<dt><a href="#init">init([options])</a></dt> | ||
<dd><p>Initialize the store with actions and listening for storage events</p> | ||
</dd> | ||
</dl> | ||
|
||
<a name="connect"></a> | ||
|
||
## connect(mapping) ⇒ <code>Number</code> | ||
Subscribes a react component's state directly to a store key | ||
|
||
**Kind**: global function | ||
**Returns**: <code>Number</code> - an ID to use when calling disconnect | ||
|
||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| mapping | <code>Object</code> | the mapping information to connect Onyx to the components state | | ||
| mapping.key | <code>String</code> | ONYXKEY to subscribe to | | ||
| [mapping.statePropertyName] | <code>String</code> | the name of the property in the state to connect the data to | | ||
| [mapping.withOnyxInstance] | <code>Object</code> | whose setState() method will be called with any changed data This is used by React components to connect to Onyx | | ||
| [mapping.callback] | <code>Object</code> | a method that will be called with changed data This is used by any non-React code to connect to Onyx | | ||
| [mapping.initWithStoredValues] | <code>Boolean</code> | If set to false, then no data will be prefilled into the component | | ||
|
||
**Example** | ||
```js | ||
const connectionID = Onyx.connect({ | ||
key: ONYXKEYS.SESSION, | ||
callback: onSessionChange, | ||
}); | ||
``` | ||
<a name="disconnect"></a> | ||
|
||
## disconnect(connectionID, [keyToRemoveFromEvictionBlocklist]) | ||
Remove the listener for a react component | ||
|
||
**Kind**: global function | ||
|
||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| connectionID | <code>Number</code> | unique id returned by call to Onyx.connect() | | ||
| [keyToRemoveFromEvictionBlocklist] | <code>String</code> | | | ||
|
||
**Example** | ||
```js | ||
Onyx.disconnect(connectionID); | ||
``` | ||
<a name="set"></a> | ||
|
||
## set(key, value) ⇒ <code>Promise</code> | ||
Write a value to our store with the given key | ||
|
||
**Kind**: global function | ||
|
||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| key | <code>String</code> | ONYXKEY to set | | ||
| value | <code>\*</code> | value to store | | ||
|
||
<a name="multiSet"></a> | ||
|
||
## multiSet(data) ⇒ <code>Promise</code> | ||
Sets multiple keys and values | ||
|
||
**Kind**: global function | ||
|
||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| data | <code>Object</code> | object keyed by ONYXKEYS and the values to set | | ||
|
||
**Example** | ||
```js | ||
Onyx.multiSet({'key1': 'a', 'key2': 'b'}); | ||
``` | ||
<a name="merge"></a> | ||
|
||
## merge(key, value) ⇒ <code>Promise</code> | ||
Merge a new value into an existing value at a key. | ||
|
||
The types of values that can be merged are `Object` and `Array`. To set another type of value use `Onyx.set()`. Merge | ||
behavior uses lodash/merge under the hood for `Object` and simple concatenation for `Array`. However, it's important | ||
to note that if you have an array value property on an `Object` that the default behavior of lodash/merge is not to | ||
concatenate. See here: https://github.com/lodash/lodash/issues/2872 | ||
|
||
Calls to `Onyx.merge()` are batched so that any calls performed in a single tick will stack in a queue and get | ||
applied in the order they were called. Note: `Onyx.set()` calls do not work this way so use caution when mixing | ||
`Onyx.merge()` and `Onyx.set()`. | ||
|
||
**Kind**: global function | ||
|
||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| key | <code>String</code> | ONYXKEYS key | | ||
| value | <code>Object</code> \| <code>Array</code> | Object or Array value to merge | | ||
|
||
**Example** | ||
```js | ||
Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Joe']); // -> ['Joe'] | ||
Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Jack']); // -> ['Joe', 'Jack'] | ||
Onyx.merge(ONYXKEYS.POLICY, {id: 1}); // -> {id: 1} | ||
Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'} | ||
``` | ||
<a name="clear"></a> | ||
|
||
## clear() ⇒ <code>Promise.<void></code> | ||
Clear out all the data in the store | ||
|
||
**Kind**: global function | ||
<a name="mergeCollection"></a> | ||
|
||
## mergeCollection(collectionKey, collection) ⇒ <code>Promise</code> | ||
Merges a collection based on their keys | ||
|
||
**Kind**: global function | ||
|
||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| collectionKey | <code>String</code> | e.g. `ONYXKEYS.COLLECTION.REPORT` | | ||
| collection | <code>Object</code> | Object collection keyed by individual collection member keys and values | | ||
|
||
**Example** | ||
```js | ||
Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, { | ||
[`${ONYXKEYS.COLLECTION.REPORT}1`]: report1, | ||
[`${ONYXKEYS.COLLECTION.REPORT}2`]: report2, | ||
}); | ||
``` | ||
<a name="init"></a> | ||
|
||
## init([options]) | ||
Initialize the store with actions and listening for storage events | ||
|
||
**Kind**: global function | ||
|
||
| Param | Type | Default | Description | | ||
| --- | --- | --- | --- | | ||
| [options] | <code>Object</code> | <code>{}</code> | config object | | ||
| [options.keys] | <code>Object</code> | <code>{}</code> | `ONYXKEYS` constants object | | ||
| [options.initialKeyStates] | <code>Object</code> | <code>{}</code> | initial data to set when `init()` and `clear()` is called | | ||
| [options.safeEvictionKeys] | <code>Array.<String></code> | <code>[]</code> | This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged as "safe" for removal. Any components subscribing to these keys must also implement a canEvict option. See the README for more info. | | ||
| [options.registerStorageEventListener] | <code>function</code> | <code>() => {}</code> | a callback when a storage event happens. This applies to web platforms where the local storage emits storage events across all open tabs and allows Onyx to stay in sync across all open tabs. | | ||
| [options.maxCachedKeysCount] | <code>Number</code> | <code>55</code> | Sets how many recent keys should we try to keep in cache Setting this to 0 would practically mean no cache We try to free cache when we connect to a safe eviction key | | ||
| [options.captureMetrics] | <code>Boolean</code> | | Enables Onyx benchmarking and exposes the get/print/reset functions | | ||
|
||
**Example** | ||
```js | ||
Onyx.init({ | ||
keys: ONYXKEYS, | ||
initialKeyStates: { | ||
[ONYXKEYS.SESSION]: {loading: false}, | ||
}, | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
# React-Native-Onyx | ||
This is a persistent storage solution wrapped in a Pub/Sub library. In general that means: | ||
# `react-native-onyx` | ||
Persistent storage solution wrapped in a Pub/Sub library. | ||
|
||
# Features | ||
|
||
- Onyx stores and retrieves data from persistent storage | ||
- Data is stored as key/value pairs, where the value can be anything from a single piece of data to a complex object | ||
|
@@ -11,7 +13,135 @@ This is a persistent storage solution wrapped in a Pub/Sub library. In general t | |
3. Get initialized with the current value of that key from persistent storage (Onyx does this by calling `setState()` or triggering the `callback` with the values currently on disk as part of the connection process) | ||
- Subscribing to Onyx keys is done using a constant defined in `ONYXKEYS`. Each Onyx key represents either a collection of items or a specific entry in storage. For example, since all reports are stored as individual keys like `report_1234`, if code needs to know about all the reports (e.g. display a list of them in the nav menu), then it would subscribe to the key `ONYXKEYS.COLLECTION.REPORT`. | ||
|
||
### Storage Eviction | ||
# Getting Started | ||
|
||
## Installation | ||
|
||
At the moment, Onyx is not yet published to `npm`. To use in your project, reference the latest sha of the main branch directly in `package.json` | ||
|
||
```json | ||
"dependencies": { | ||
"react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#ccb64c738b8bbe933b8997eb177f864e5139bd8d" | ||
} | ||
``` | ||
|
||
## Initialization | ||
|
||
To initialize Onyx we call `Onyx.init()` with a configuration object like so | ||
|
||
```javascript | ||
import Onyx from 'react-native-onyx'; | ||
|
||
const ONYXKEYS = { | ||
SESSION: 'session', | ||
}; | ||
|
||
const config = { | ||
keys: ONYXKEYS, | ||
}; | ||
|
||
Onyx.init(config); | ||
``` | ||
|
||
## Setting data | ||
|
||
To store some data we can use the `Onyx.set()` method. | ||
|
||
```javascript | ||
API.Authenticate(params) | ||
.then((response) => { | ||
Onyx.set(ONYXKEYS.SESSION, {token: response.token}); | ||
}); | ||
``` | ||
|
||
The data will then be cached and stored via [`AsyncStorage`](https://github.com/react-native-async-storage/async-storage). | ||
|
||
## Merging data | ||
|
||
We can also use `Onyx.merge()` to merge new `Object` or `Array` data in with existing data. | ||
|
||
For `Array` the default behavior is to concatenate new items. | ||
|
||
```javascript | ||
Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Joe']); // -> ['Joe'] | ||
Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Jack']); // -> ['Joe', 'Jack'] | ||
``` | ||
|
||
For `Object` values the default behavior uses `lodash/merge` under the hood to do a deep extend of the object. | ||
|
||
```javascript | ||
Onyx.merge(ONYXKEYS.POLICY, {id: 1}); // -> {id: 1} | ||
Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'} | ||
``` | ||
|
||
One caveat to be aware of is that `lodash/merge` [follows the behavior of jQuery's deep extend](https://github.com/lodash/lodash/issues/2872) and will not concatenate nested arrays in objects. It might seem like this code would concat these arrays, but it does not. | ||
|
||
```javascript | ||
Onyx.merge(ONYXKEYS.POLICY, {employeeList: ['Joe']}); // -> {employeeList: ['Joe']} | ||
Onyx.merge(ONYXKEYS.POLICY, {employeeList: ['Jack']}); // -> {employeeList: ['Jack']} | ||
``` | ||
Luke9389 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### Should I use `merge()` or `set()` or both? | ||
|
||
- Use `merge()` if we want to merge partial data into an existing `Array` or `Object` | ||
- Use `set()` if we are working with simple values (`String`, `Boolean`, etc), need to completely overwrite a complex property of an `Object`, or reset some data entirely. | ||
|
||
Consecutive calls to `Onyx.merge()` with the same key are batched in a stack and processed in the order that they were called. This helps avoid race conditions where one merge possibly finishes before another. However, it's important to note that calls to `Onyx.set()` are not batched together with calls to `Onyx.merge()`. For this reason, it is usually preferable to use one or the other, but not both. Onyx is a work-in-progress so always test code to make sure assumptions are correct! | ||
|
||
## Subscribing to data changes | ||
|
||
To set up a basic subscription for a given key use the `Onyx.connect()` method. | ||
|
||
```javascript | ||
let session; | ||
const connectionID = Onyx.connect({ | ||
key: ONYXKEYS.SESSION, | ||
callback: (val) => session = val || {}, | ||
}); | ||
``` | ||
|
||
To teardown the subscription call `Onyx.disconnect()` with the `connectionID` returned from `Onyx.connect()`. It's recommended to clean up subscriptions anytime you are connecting from within a function to prevent memory leaks. | ||
|
||
```javascript | ||
Onyx.disconnect(connectionID); | ||
``` | ||
|
||
We can also access values inside React components via the `withOnyx()` [higher order component](https://reactjs.org/docs/higher-order-components.html). When the data changes the component will re-render. | ||
|
||
```javascript | ||
import React from 'react'; | ||
import {withOnyx} from 'react-native-onyx'; | ||
|
||
const App = ({session}) => ( | ||
<View> | ||
{session.token ? <Text>Logged in</Text> : <Text>Logged out</Text> } | ||
</View> | ||
); | ||
|
||
export default withOnyx({ | ||
session: { | ||
key: ONYXKEYS.SESSION, | ||
}, | ||
})(App); | ||
``` | ||
|
||
It is preferable to use the HOC over `Onyx.connect()` in React code as `withOnyx()` will delay the rendering of the wrapped component until all keys have been accessed and made available. | ||
|
||
## Clean up | ||
|
||
To clear all data from `Onyx` we can use `Onyx.clear()`. | ||
|
||
```javascript | ||
function signOut() { | ||
Onyx.clear(); | ||
} | ||
``` | ||
Luke9389 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# API Reference | ||
|
||
[Docs](./API.md) | ||
|
||
# Storage Eviction | ||
|
||
Different platforms come with varying storage capacities and Onyx has a way to gracefully fail when those storage limits are encountered. When Onyx fails to set or modify a key the following steps are taken: | ||
1. Onyx looks at a list of recently accessed keys (access is defined as subscribed to or modified) and locates the key that was least recently accessed | ||
|
@@ -40,7 +170,7 @@ export default withOnyx({ | |
})(ReportActionsView); | ||
``` | ||
|
||
### Benchmarks | ||
# Benchmarks | ||
|
||
Provide the `captureMetrics` boolean flag to `Onyx.init` to capture call statistics | ||
|
||
|
@@ -65,7 +195,7 @@ If you wish to reset the metrics and start over use `Onyx.resetMetrics()` | |
Finally, there's a `Onyx.printMetrics()` method which prints human statistics information on the dev console | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a |
||
You can use this method during debugging. For example add an `Onyx.printMetrics()` line somewhere in code or call it | ||
through the dev console. It supports 3 popular formats *MD* - human friendly markdown, *CSV* and *JSON* | ||
The default is MD if you want to print another format call `Onyx.printMetrics({ format: 'csv' })` or | ||
The default is MD if you want to print another format call `Onyx.printMetrics({ format: 'csv' })` or | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a |
||
`Onyx.printMetrics({ format: 'json' })` | ||
|
||
Sample output of `Onyx.printMetrics()` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
const fs = require('fs'); | ||
const jsdoc2md = require('jsdoc-to-markdown'); | ||
|
||
// eslint-disable-next-line no-console | ||
jsdoc2md.render({files: 'lib/Onyx.js'}).then((docs) => { | ||
// eslint-disable-next-line max-len | ||
let heading = '<!---These docs were automatically generated. Do not edit them directly run `npm run build-docs` script-->\n\n# API Reference\n\n'; | ||
heading += docs; | ||
fs.writeFileSync('API.md', heading); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.