Comparing version 2.0.4 to 2.1.1
@@ -1,2 +0,2 @@ | ||
import { Collection, apiClient } from '../src' | ||
import { Collection, apiClient, Request } from '../src' | ||
import MockApi from './mocks/api' | ||
@@ -64,3 +64,3 @@ | ||
it('returns false if the label does not match', () => { | ||
collection.request = { label: 'creating' } | ||
collection.request = new Request('creating', null, 0) | ||
expect(collection.isRequest('fetching')).toBe(false) | ||
@@ -70,3 +70,3 @@ }) | ||
it('returns true otherwie', () => { | ||
collection.request = { label: 'fetching' } | ||
collection.request = new Request('fetching', null, 0) | ||
expect(collection.isRequest('fetching')).toBe(true) | ||
@@ -73,0 +73,0 @@ }) |
@@ -1,2 +0,2 @@ | ||
import { Collection, Model, apiClient } from '../src' | ||
import { Collection, Model, apiClient, Request } from '../src' | ||
import MockApi from './mocks/api' | ||
@@ -44,2 +44,21 @@ | ||
describe('isRequest', () => { | ||
it('returns false if there is no request', () => { | ||
const newModel = new MyModel({}) | ||
expect(newModel.isRequest('fetching')).toBe(false) | ||
}) | ||
it('return false if the request is something different', () => { | ||
const newModel = new MyModel({}) | ||
newModel.request = new Request('creating', null, 0) | ||
expect(newModel.isRequest('fetching')).toBe(false) | ||
}) | ||
it('return true if the request is matching', () => { | ||
const newModel = new MyModel({}) | ||
newModel.request = new Request('fetching', null, 0) | ||
expect(newModel.isRequest('fetching')).toBe(true) | ||
}) | ||
}) | ||
describe('isNew', () => { | ||
@@ -46,0 +65,0 @@ it('returns true if it does not have an id', () => { |
@@ -263,3 +263,3 @@ 'use strict'; | ||
/** | ||
* Sets the models into the collection. | ||
* Sets the resources into the collection. | ||
* | ||
@@ -271,3 +271,3 @@ * You can disable adding, changing or removing. | ||
key: 'set', | ||
value: function set(models) { | ||
value: function set(resources) { | ||
var _this3 = this; | ||
@@ -284,4 +284,4 @@ | ||
if (remove) { | ||
var ids = models.map(function (d) { | ||
return d.id; | ||
var ids = resources.map(function (r) { | ||
return r.id; | ||
}); | ||
@@ -292,7 +292,7 @@ var toRemove = (0, _lodash.difference)(this._ids(), ids); | ||
models.forEach(function (attributes) { | ||
var model = _this3.get(attributes.id); | ||
resources.forEach(function (resource) { | ||
var model = _this3.get(resource.id); | ||
if (model && change) model.set(attributes); | ||
if (!model && add) _this3.add([attributes]); | ||
if (model && change) model.set(resource); | ||
if (!model && add) _this3.add([resource]); | ||
}); | ||
@@ -352,3 +352,5 @@ } | ||
}, 300); | ||
_apiClient$post = (0, _apiClient2.default)().post(this.url(), attributes, { onProgress: onProgress }), abort = _apiClient$post.abort, promise = _apiClient$post.promise; | ||
_apiClient$post = (0, _apiClient2.default)().post(this.url(), attributes, { | ||
onProgress: onProgress | ||
}), abort = _apiClient$post.abort, promise = _apiClient$post.promise; | ||
@@ -355,0 +357,0 @@ |
@@ -6,3 +6,3 @@ 'use strict'; | ||
}); | ||
exports.apiClient = exports.Model = exports.Collection = undefined; | ||
exports.Request = exports.apiClient = exports.Model = exports.Collection = undefined; | ||
@@ -17,2 +17,6 @@ var _Collection = require('./Collection'); | ||
var _Request = require('./Request'); | ||
var _Request2 = _interopRequireDefault(_Request); | ||
var _apiClient = require('./apiClient'); | ||
@@ -26,2 +30,3 @@ | ||
exports.Model = _Model2.default; | ||
exports.apiClient = _apiClient2.default; | ||
exports.apiClient = _apiClient2.default; | ||
exports.Request = _Request2.default; |
@@ -110,4 +110,4 @@ 'use strict'; | ||
/** | ||
* Return the base url used in | ||
* the `url` method | ||
* Determine what attribute do you use | ||
* as a primary id | ||
* | ||
@@ -119,2 +119,10 @@ * @abstract | ||
key: 'urlRoot', | ||
/** | ||
* Return the base url used in | ||
* the `url` method | ||
* | ||
* @abstract | ||
*/ | ||
value: function urlRoot() { | ||
@@ -146,3 +154,3 @@ throw new Error('`url` method not implemented'); | ||
} else { | ||
return urlRoot + '/' + this.get('id'); | ||
return urlRoot + '/' + this.get(this.primaryKey); | ||
} | ||
@@ -152,6 +160,19 @@ } | ||
/** | ||
* Questions whether the request exists | ||
* and matches a certain label | ||
*/ | ||
}, { | ||
key: 'isRequest', | ||
value: function isRequest(label) { | ||
if (!this.request) return false; | ||
return this.request.label === label; | ||
} | ||
/** | ||
* Wether the resource is new or not | ||
* | ||
* We determine this asking if it contains | ||
* the `id` attribute (set by the server). | ||
* the `primaryKey` attribute (set by the server). | ||
*/ | ||
@@ -276,3 +297,3 @@ | ||
* | ||
* If the item has an `id` it updates it, | ||
* If the item has a `primaryKey` it updates it, | ||
* otherwise it creates the new resource. | ||
@@ -303,3 +324,3 @@ * | ||
case 0: | ||
if (this.has('id')) { | ||
if (this.has(this.primaryKey)) { | ||
_context2.next = 7; | ||
@@ -424,3 +445,5 @@ break; | ||
}, 300); | ||
_apiClient$post = (0, _apiClient2.default)().post(this.url(), attributes, { onProgress: onProgress }), abort = _apiClient$post.abort, promise = _apiClient$post.promise; | ||
_apiClient$post = (0, _apiClient2.default)().post(this.url(), attributes, { | ||
onProgress: onProgress | ||
}), abort = _apiClient$post.abort, promise = _apiClient$post.promise; | ||
@@ -499,3 +522,3 @@ | ||
case 0: | ||
if (!(!this.has('id') && this.collection)) { | ||
if (!(!this.has(this.primaryKey) && this.collection)) { | ||
_context4.next = 3; | ||
@@ -634,5 +657,10 @@ break; | ||
}, { | ||
key: 'primaryKey', | ||
get: function get() { | ||
return 'id'; | ||
} | ||
}, { | ||
key: 'isNew', | ||
get: function get() { | ||
return !this.has('id'); | ||
return !this.has(this.primaryKey); | ||
} | ||
@@ -642,3 +670,3 @@ }, { | ||
get: function get() { | ||
return this.has('id') ? this.get('id') : this.optimisticId; | ||
return this.has(this.primaryKey) ? this.get(this.primaryKey) : this.optimisticId; | ||
} | ||
@@ -645,0 +673,0 @@ |
{ | ||
"name": "mobx-rest", | ||
"version": "2.0.4", | ||
"version": "2.1.1", | ||
"description": "REST conventions for mobx.", | ||
@@ -11,7 +11,18 @@ "repository": { | ||
"jest": { | ||
"testRegex": "/__tests__/.*\\.spec\\.js$" | ||
"collectCoverage": true, | ||
"testRegex": "/__tests__/.*\\.spec\\.js$", | ||
"collectCoverageFrom": [ | ||
"src/**/*.js" | ||
] | ||
}, | ||
"standard": { | ||
"parser": "babel-eslint", | ||
"globals": [ "it", "describe", "beforeEach", "expect", "Class", "jest" ] | ||
"globals": [ | ||
"it", | ||
"describe", | ||
"beforeEach", | ||
"expect", | ||
"Class", | ||
"jest" | ||
] | ||
}, | ||
@@ -27,2 +38,3 @@ "dependencies": { | ||
"babel-jest": "^19.0.0", | ||
"babel-plugin-transform-async-to-generator": "^6.22.0", | ||
"babel-plugin-transform-decorators-legacy": "^1.3.4", | ||
@@ -34,16 +46,34 @@ "babel-plugin-transform-flow-strip-types": "^6.22.0", | ||
"babel-register": "^6.23.0", | ||
"eslint": "^3.18.0", | ||
"eslint-config-standard": "^7.1.0", | ||
"eslint-plugin-flowtype": "2.30.3", | ||
"eslint-plugin-import": "^2.2.0", | ||
"eslint-plugin-node": "^4.2.1", | ||
"eslint-plugin-promise": "^3.5.0", | ||
"eslint-plugin-standard": "^2.1.1", | ||
"flow-bin": "^0.41.0", | ||
"husky": "^0.13.2", | ||
"jest": "^19.0.2", | ||
"snazzy": "^6.0.0", | ||
"standard": "^9.0.1" | ||
"lint-staged": "^3.4.0", | ||
"prettier-standard": "^1.0.6" | ||
}, | ||
"main": "lib", | ||
"scripts": { | ||
"compile": "./node_modules/.bin/babel src --out-dir lib", | ||
"compile": "babel src --out-dir lib", | ||
"prepublish": "npm run compile", | ||
"jest": "BABEL_ENV=test NODE_PATH=src jest --no-cache", | ||
"lint": "standard --verbose | snazzy", | ||
"lint": "eslint src __tests__", | ||
"flow": "flow", | ||
"test": "npm run flow && npm run lint && npm run jest" | ||
"test": "npm run flow && npm run lint && npm run jest", | ||
"format": "prettier-standard --print-width 60 'src/**/*.js'", | ||
"prepush": "npm test", | ||
"lint-staged": { | ||
"linters": { | ||
"src/**/*.js": [ | ||
"prettier-standard", | ||
"git add" | ||
] | ||
} | ||
} | ||
} | ||
} |
511
README.md
@@ -1,2 +0,2 @@ | ||
# REST Mobx | ||
# mobx-rest | ||
@@ -6,2 +6,3 @@ REST conventions for mobx. | ||
[![Build Status](https://travis-ci.org/masylum/mobx-rest.svg?branch=master)](https://travis-ci.org/masylum/mobx-rest) | ||
[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](http://standardjs.com) | ||
@@ -18,11 +19,383 @@ ![](https://media.giphy.com/media/b9QBHfcNpvqDK/giphy.gif) | ||
MobX is great to represent RESTful resources. Each resource can be represented | ||
with a store which will the expected REST actions (`create`, `fetch`, `save`, `destroy`, ...). | ||
An application state is usually divided into three realms: | ||
Instead of writing hundreds of boilerplate lines we can leverage REST conventions | ||
to deal with your API interactions. | ||
- **Component state**: Each state can have their own state, like a button | ||
being pressed, a text input value, etc. | ||
- **Application state**: Sometimes we need components to share state between them and | ||
they are too far away to actually make them talk each other through props. | ||
- **Resources state**: Other times, state is persisted in the server. We syncronize | ||
that state through APIs that consume *resources*. One way to syncronize this state | ||
is through REST. | ||
## Example | ||
MobX is an excellent state management choice to deal with those three realms: | ||
It allows you to represent your state as a graph while other solutions, | ||
like Redux for instance, force you to represent your state as a tree. | ||
With `mobx-rest` resources are implemented with all their REST | ||
actions built in (`create`, `fetch`, `save`, `destroy`, ...) so instead | ||
of writing, over and over, hundreds of lines of boilerplate we can leverage | ||
REST conventions to minimize the code needed for your API interactions. | ||
## Documentation | ||
`mobx-rest` is very simple and its source code can be read in 5 minutes. | ||
### `Model` | ||
A `Model` represents one resource. It's identified by a primary key (mandatory) and holds | ||
its attributes. You can create, update and destroy models in the client and then sync | ||
them with the server. A part from its attributes, a `Model` also holds the state of | ||
the interactions with the server so you can react to those easily (showing loading states | ||
for instance). | ||
#### `attributes: ObservableMap` | ||
An `ObservableMap` that holds the attributes of the model. | ||
#### `collection: ?Collection` | ||
A pointer to a `Collection`. By having models | ||
"belong to" a collection you can take the most out | ||
of `mobx-rest`. | ||
#### `request` | ||
A `Request` object that represents the state of the ongoing request, if any. | ||
#### `error` | ||
An `Error` object that represents the state of the failed request, if any. | ||
#### `constructor(attributes: Object)` | ||
Initialize the model with the given attributes. | ||
#### `toJS(): Object` | ||
Return the object version of the attributes. | ||
#### `primaryKey: string` | ||
Implement this abstract method so `mobx-rest` knows what to | ||
use as a primary key. It defaults to `'id'` but if you use | ||
something like mongodb you can change it to `'_id'`. | ||
#### `urlRoot(): string` | ||
Implement this abstract method so `mobx-rest` knows where | ||
its API points to. If the model belongs to a `Collection` | ||
(setting the `collection` attribute) this method does | ||
not need to be implemented. | ||
#### `url(): string` | ||
Return the url for that given resource. Will leverage the | ||
collection's base url (if any) or `urlRoot`. It uses the | ||
primary id since that's REST convention. | ||
Example: `tasks.get(34).url() // => "/tasks/34"` | ||
#### `isRequest(label: string): boolean` | ||
Helper method that asks the model whether there is an ongoing | ||
request with the given label. | ||
Example: `file.isRequest('saving')` | ||
#### `isNew: boolean` | ||
Return whether that model has been syncronized with the server or not. | ||
Resources created in the client side (optimistically) don't have | ||
and `id` attribute yet (that's given by the server) | ||
Example: | ||
```js | ||
const user = new User({ name : 'Pau' }) | ||
user.isNew // => true | ||
user.save() | ||
user.isNew // => false | ||
user.get('id') // => 1 | ||
``` | ||
#### `get(attribute: string): any` | ||
Get the given attribute. If the attribute does not exist, it will throw. | ||
#### `has(attribute: string): boolean` | ||
Check that the given attribute exists. | ||
#### `set(data: Object): void` | ||
Update the attributes in the client. | ||
Example: | ||
```js | ||
const folder = new Folder({ name : 'Trash' }) | ||
folder.get('name') // => 'Trash' | ||
folder.set({ name: 'Rubbish' }) | ||
folder.get('name') // => 'Rubbish' | ||
``` | ||
#### `fetch(options): Promise` | ||
Request this resource's data from the server. It tracks the state | ||
of the request using the label `fetching` and updates the resource when | ||
the data is back from the API. | ||
Example: | ||
```js | ||
const task = new Task({ id: 3 }) | ||
const promise = task.fetch() | ||
task.isRequest('fetching') // => true | ||
await promise | ||
task.get('name') // => 'Do the laundry' | ||
``` | ||
#### `save(attributes: Object, options: Object): Promise` | ||
The opposite of `fetch`. It takes the resource from the client and | ||
persists it in the server through the API. It accepts some attributes | ||
as the first argument so you can use it as a `set` + `save`. | ||
It tracks the state of the request using the label `saving`. | ||
Options: | ||
- `optimistic = true` Whether we want to update the resource in the client | ||
first or wait for the server's response. | ||
- `patch = true` Whether we want to use the `PATCH` verb and semantics, sending | ||
only the changed attributes instead of the whole resource down the wire. | ||
Example: | ||
```js | ||
const company = new Company({ name: 'Teambox' }) | ||
const promise = company.save({ name: 'Redbooth' }, { optimstic: false }) | ||
company.isRequest('saving') // => true | ||
company.get('name') // => 'Teambox' | ||
await promise | ||
company.get('name') // => 'Redbooth' | ||
``` | ||
#### `destroy(options: Object): Promise` | ||
Tells the API to destroy this resource. | ||
Options: | ||
- `optimistic = true` Whether we want to delete the resource in the client | ||
first or wait for the server's response. | ||
#### `rpc(method: 'string', body: {}): Promise` | ||
When dealing with REST there are always cases when we have some actions beyond | ||
the conventions. Those are represented as `rpc` calls and are not opinionated. | ||
Example: | ||
```js | ||
const response = await task.rpc('resolveSubtasks', { all: true }) | ||
if (response.ok) { | ||
task.subTasks.fetch() | ||
} | ||
``` | ||
### `Collection` | ||
A `Collection` represents a group of resources. Each element of a `Collection` is a `Model`. | ||
Likewise, a collection tracks also the state of the interactions with the server so you | ||
can react accordingly. | ||
#### `models: ObservableArray` | ||
An `ObservableArray` that holds the collection of models. | ||
#### `request: ?Request` | ||
A `Request` object that represents the state of the ongoing request, if any. | ||
#### `error: ?ErrorObject` | ||
An `Error` object that represents the state of the failed request, if any. | ||
#### `constructor(data: Array<Object>)` | ||
Initializes the collection with the given resources. | ||
#### `url(): string` | ||
Abstract method that must be implemented if you want your collection | ||
and it's models to be able to interact with the API. | ||
#### `model(): Model` | ||
Abstract method that tells which kind of `Model` objects this collection | ||
holds. This is used, for instance, when doing a `collection.create` so | ||
we know which object to instantiate. | ||
#### `toJS(): Array<Object>` | ||
Return a plain data structure representing the collection of resources | ||
without all the observable layer. | ||
#### `toArray(): Array<ObservableMap>` | ||
Return an array with the observable resources. | ||
#### `isRequest(label: string): boolean` | ||
Helper method that asks the collection whether there is an ongoing | ||
request with the given label. | ||
Example: | ||
```js | ||
filesCollection.isRequest('saving') | ||
``` | ||
#### `isEmpty(): boolean` | ||
Helper method that asks the collection whether there is any | ||
model in it. | ||
Example: | ||
```js | ||
const promise = usersCollection.fetch() | ||
usersCollection.isEmpty() // => true | ||
await promise | ||
usersCollection.isEmpty() // => false | ||
usersCollection.models.length // => 10 | ||
``` | ||
#### `at(index: number): ?Model` | ||
Find a model at the given position. | ||
#### `get(id: number): ?Model` | ||
Find a model (or not) with the given id. | ||
#### `filter(query: Object): Array<Model>` | ||
Helper method that filters the collection by the given conditions represented | ||
as a key value. | ||
Example: | ||
```js | ||
const resolvedTasks = tasksCollection.filter({ resolved: true }) | ||
resolvedTasks.length // => 3 | ||
``` | ||
#### `find(query: Object): ?Model` | ||
Same as `filter` but it will halt and return when the first model matches | ||
the conditions. | ||
Example: | ||
```js | ||
const pau = usersCollection.find({ name: 'pau' }) | ||
pau.get('name') // => 'pau' | ||
``` | ||
#### `add(data: Object): Array<Model>` | ||
Add a model with the given attributes. | ||
#### `remove(ids: Array<number>): void` | ||
Remove any model with the given ids. | ||
Example: | ||
```js | ||
usersCollection.remove([1, 2, 3]) | ||
``` | ||
#### `set(models: Array<Object>, options: Object): void` | ||
Merge the given models smartly the current ones in the collection. | ||
It detects what to add, remove and change. | ||
Options: | ||
- `add = true` Change to disable adding models | ||
- `change = true` Change to disable updating models | ||
- `remove = true` Change to disable removing models | ||
```js | ||
const companiesCollection = new CompaniesCollection([ | ||
{ id: 1, name: 'Teambox' } | ||
{ id: 3, name: 'Zpeaker' } | ||
]) | ||
companiesCollection.set([ | ||
{ id: 1, name: 'Redbooth' }, | ||
{ id: 2, name: 'Factorial' } | ||
]) | ||
companiesCollection.get(1).get('name') // => 'Redbooth' | ||
companiesCollection.get(2).get('name') // => 'Factorial' | ||
companiesCollection.get(3) // => null | ||
``` | ||
#### `build(attributes: Object): Model` | ||
Instantiates and links a model to the current collection. | ||
```js | ||
const factorial = companiesCollection.build({ name: 'Factorial' }) | ||
factorial.collection === companiesCollection // => true | ||
factorial.get('name') // 'Factorial' | ||
``` | ||
#### `create(target: Object | Model, options: Object)` | ||
Add and save to the server the given model. If attributes are given, | ||
also it builds the model for you. It tracks the state of the request | ||
using the label `creating`. | ||
Options: | ||
- `optimistic = true` Whether we want to create the resource in the client | ||
first or wait for the server's response. | ||
```js | ||
const promise = tasksCollection.create({ name: 'Do laundry' }) | ||
tasksCollection.isRequest('creating') // => true | ||
await promise | ||
tasksCollection.at(0).get('name') // => 'Do laundry' | ||
``` | ||
#### `fetch(options: Object)` | ||
Fetch the date from the server and then calls `set` to update the current | ||
models. Accepts any option from the `set` method. | ||
```js | ||
const promise = tasksCollection.fetch() | ||
tasksCollection.isEmpty() // => true | ||
tasksCollection.isRequest('fetching') // => true | ||
await promise | ||
tasksCollection.isEmpty() // => false | ||
``` | ||
#### `rpc(method: 'string', body: {}): Promise` | ||
Exactly the same as the model one, but at the collection level. | ||
### `apiClient` | ||
This is the object that is going to make the `xhr` requests to interact with your API. | ||
There is an example implementation for jQuery in the `mobx-rest-jquery-adapter` package. | ||
## Full Example | ||
A collection looks like this: | ||
```js | ||
// TasksCollection.js | ||
const apiPath = '/api' | ||
@@ -32,3 +405,3 @@ import jqueryAdapter from 'mobx-rest-jquery-adapter' | ||
// Set the adapter | ||
// We will use the jQuery adapter to make the `xhr` calls | ||
apiClient(jqueryAdapter, { apiPath }) | ||
@@ -38,31 +411,58 @@ | ||
class Tasks extends Collection { | ||
url () { | ||
return `/tasks` | ||
} | ||
model () { | ||
return Task | ||
} | ||
url () { return `/tasks` } | ||
model () { return Task } | ||
} | ||
const tasks = new Tasks() | ||
// We instantiate the collection and export it as a singleton | ||
export default new Tasks() | ||
``` | ||
And here an example of how to use React with it: | ||
```js | ||
import tasksCollection from './TasksCollection' | ||
import { computed } from 'mobx' | ||
import { observer } from 'mobx-react' | ||
@observer | ||
class Companies extends React.Component { | ||
class Task extends React.Component { | ||
onClick () { | ||
this.props.task.save({ resolved: true }) | ||
} | ||
render () { | ||
return ( | ||
<li key={task.id}> | ||
<button onClick={this.onClick.bind(this)}> | ||
resolve | ||
</button> | ||
{this.props.task.get('name')} | ||
</li> | ||
) | ||
} | ||
} | ||
@observer | ||
class Tasks extends React.Component { | ||
componentWillMount () { | ||
tasks.fetch() | ||
// This will call `/api/tasks?all=true` | ||
tasksCollection.fetch({ data: { all: true } }) | ||
} | ||
renderTask (task, i) { | ||
return <li key={i}><Task task={task} /></li> | ||
@computed | ||
get activeTasks () { | ||
return tasksCollection.filter({ resolved: false }) | ||
} | ||
render () { | ||
if (tasks.isRequest('fetching')) { | ||
if (tasksCollection.isRequest('fetching')) { | ||
return <span>Fetching tasks...</span> | ||
} | ||
return <ul>{tasks.models.map(this.renderTask.bind(this))}</ul> | ||
return ( | ||
<div> | ||
<span>{this.activeTasks.length} tasks</span> | ||
<ul>{activeTasks.map((task) => <Task task={task} />)}</ul> | ||
</div> | ||
) | ||
} | ||
@@ -73,28 +473,17 @@ } | ||
## Tree schema | ||
## State shape | ||
Your tree will have the following schema: | ||
Your collections and models will have the following state shape: | ||
### Collection | ||
```js | ||
models: [ | ||
{ // Information at the resource level | ||
optimisticId: String, // Client side id. Used for optimistic updates | ||
request: { // An ongoing request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
abort: Function, // A method to abort the ongoing request | ||
}, | ||
error: { // A failed request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
body: String, // A string representing the error | ||
}, | ||
attributes: Object // The resource attributes | ||
} | ||
] // Information at the collection level | ||
models: Array<Model> // This is where the models live | ||
request: { // An ongoing request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
abort: Function, // A method to abort the ongoing request | ||
label: string, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
abort: () => void, // A method to abort the ongoing request | ||
progress: number // If uploading a file, represents the progress | ||
}, | ||
error: { // A failed request | ||
label: String, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
label: string, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
body: Object, // A string representing the error | ||
@@ -104,2 +493,44 @@ } | ||
### Model | ||
```js | ||
attributes: Object // The resource attributes | ||
optimisticId: string, // Client side id. Used for optimistic updates | ||
request: { // An ongoing request | ||
label: string, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
abort: () => void, // A method to abort the ongoing request | ||
}, | ||
error: { // A failed request | ||
label: string, // Examples: 'updating', 'creating', 'fetching', 'destroying' ... | ||
body: string, // A string representing the error | ||
}, | ||
``` | ||
## FAQ | ||
### How do I create relations between the models? | ||
This is something that mobx makes really easy to achieve: | ||
```js | ||
import usersCollection from './UsersCollections' | ||
import { computed } from 'mobx' | ||
class Task extends Model { | ||
@computed | ||
author () { | ||
const userId = this.get('userId') | ||
return usersCollection.get(userId) || | ||
usersCollection.nullObject() | ||
} | ||
} | ||
``` | ||
I recommend to always fallback with a null object which will facilitate | ||
a ton to write code like `task.author.get('name')`. | ||
## Where is it used? | ||
Developed and battle tested in production in [Factorial](https://factorialhr.com) | ||
## License | ||
@@ -106,0 +537,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
441329
57
6978
541
23
3