redux-storm
v1.0.10
Published
![logo](./redux-storm.png?raw=true)
Downloads
5
Readme
Strongly Talented ORM
Prerequisites
React-redux: this is a plugin for React-Redux, therefore you must have it installed before you can start.
Installation
npm install redux-storm --save
Usage
Storm supports two types of data: collections and sets. The first is used to handle array-like data, the latter handles object-like data.
Collections
Let's say you need to store a list of users coming from an API. You need to create a model for that;
class Users {
static $type = 'collection';
static $table = 'users';
static schema() {
return {
id: null,
name: ''
}
}
}
The syntax above is allowed only if you have the
@babel/plugin-proposal-class-properties
plugin installed. If you don't want to use the babel plugin you can turn that declaration into something like the snippet you'll find below. However, this documentation will tend to use the syntax from the first example
const Users = {
$type: 'collection',
$table: 'users',
schema() {
return {
id: null,
name: ''
}
}
}
What have we done with that?
We defined a table name ($table) for this model (that will be used to create the reducer in redux) and a type ($type), telling to storm
to treat this model as a collection of users; we then defined a schema function that returns the shape of the user, within the default values (can be null).
Then we need to tell redux to use our model, so we add this to the reducers registration:
import { combineReducers } from 'redux';
import Users from '../models/Users';
// use this function to register your models
import { register } from 'redux-storm';
const reducers = combineReducers({
// other reducers you might be using
// ......
//
// also, this method accepts multiple models, separated by a comma, like an array
...register(Users),
});
export default reducers;
That's it! We are ready to go and implement the logic!
Let's have a look to a possible implementation
import React, { useEffect } from 'react';
import { useModel } from 'redux-storm';
import UserModel from '../models/Users';
function UserList() {
const Users = useModel(UserModel);
const listOfUsers = Users.all();
useEffect(() => {
fetch('/api/users').then(r => r.json()).then(data => {
Users.insert(data);
});
}, []);
return listOfUsers.map(user => <div key={user.id}>{ user.name });
}
We want to make sure though that duplicates are not added, therefore we need a primary key
class Users {
static $type = 'collection';
static $table = 'users';
static $primary = 'id';
static schema() {
return {
id: null,
name: ''
}
}
}
Now if we add another user within the same ID (this however shouldn't be a thing!), it won't be added because.
When you are using a collection, you can also define a state ($state) for you model
class Users {
static $type = 'collection';
static $table = 'users';
static $state() {
return {
fetching: true
}
}
static schema() {
return {
id: null,
name: ''
}
}
}
We can now use the state inside our component, to show, for example, a loader while fetching the data
import React, { useEffect } from 'react';
import { useModel } from 'redux-storm';
import UserModel from '../models/Users';
function UserList() {
const Users = useModel(UserModel);
const listOfUsers = Users.all();
useEffect(() => {
fetch('/api/users').then(r => r.json()).then(data => {
Users.insert(data);
});
}, []);
if (User.state().fetching) { return ( <div>Loading</div> )};
return listOfUsers.map(user => <div key={user.id}>{ user.name });
}
But how do we tell now the fetching is completed? We'll use the .commit
method:
import React, { useEffect } from 'react';
import { useModel } from 'redux-storm';
import UserModel from '../models/Users';
function UserList() {
const Users = useModel(UserModel);
const listOfUsers = Users.all();
useEffect(() => {
fetch('/api/users').then(r => r.json()).then(data => {
Users.insert(data);
Users.commit(state => {
state.fetching = false;
return state;
})
});
}, []);
if (User.state().fetching) {
return ( <div>Loading</div> )
};
return listOfUsers.map(user => <div key={user.id}>{ user.name });
}
The component will now react accordingly and display the list when the users table is populated.
useModel for collections
When used on a collection, the useModel
exposes the following methods:
all(): Array
Returns all the records for the specified model
find(predicateOrPrimaryKey: Function | Any) : Array| Object
Returns an array of records given the specified condition. Example:
const Users = useModel(UserModel);
const listOfUsers = Users.find(1); // returns the user matching the primary key
const listOfUsers2 = Users.find((user) => user.name === 'me'); // returns all the user with the name 'me'
exists(): Boolean
Returns whether the table exists. Likely to be false before any data gets added to the store.
any(): Boolean
Returns whether the table contains any data.
state(): Object
Returns the state defined in the model.
commit(changes: Function)
Allows to mutate the state of the model.
insert(data: Array | Object)
Adds a record or multiple records preventing any duplication (using the primary key or the ID to distinct)
update(data: Object, predicateOrPrimaryKey: Function | Any)
Updated a record or multiple records according to the predicate Example:
const Users = useModel(UserModel);
// updates the name to 'another me' to the user with ID 1
Users.update({
name: 'another me'
}, 1);
// updates the name to 'another me' to every user whose age is greater that 10
Users.update({
name: 'another me'
}, (user) => user.age > 10);
delete(predicateOrPrimaryKey: Function | Any)
Deleted the record or multiple records according to the predicate
const Users = useModel(UserModel);
// deletes the user with ID 1
Users.delete(1);
// deletes to every user whose name is nome me
Users.delete((user) => user.name !== 'me');
insertOrUpdate(data: Array | Object, predicateOrPrimaryKey: Function | Any)
Adds a new record or mulitple records if the ones matching the predicate or the primary key are not found
Example:
const Users = useModel(UserModel);
// adds the user to the store
Users.insert({
id: 1
name: 'me'
});
// it only adds 'another me' but updates 'me' with 'me again' because they have the same ID
Users.insertOrUpdate([
{
id: 2,
name: 'another me'
},
{
id: 1,
name: 'me again'
}
])
Sets
Let's say we need to store whether the user is authenticated or not in our application. For this purpose we are going to create a different type of model:
class Authentication {
static $type = 'set';
static $table = 'auth';
static schema() {
return {
isLoggedIn: false,
username: null
}
}
}
then we add it to store like we did for the Users model:
import { combineReducers } from 'redux';
import Users from '../models/Users';
import Authentication from '../models/Authentication';
// use this function to register your models
import { register } from 'redux-storm';
const reducers = combineReducers({
// other reducers you might be using
// ......
//
...register(
Users,
Authentication
),
});
export default reducers;
Let's return to our component:
import React, { useEffect } from 'react';
import { useModel } from 'redux-storm';
import UserModel from '../models/Users';
import AuthenticationModel from '../models/Authentication';
function UserList() {
const Users = useModel(UserModel);
const Auth = useModel(AuthenticationModel);
const loggedUser = Auth.get();
const listOfUsers = Users.all();
useEffect(() => {
Auth.set('username', 'me1245');
Auth.set('isLoggedIn', true);
fetch('/api/users').then(r => r.json()).then(data => {
Users.insert(data);
});
}, []);
if (User.state().fetching) { return ( <div>Loading</div> )};
return (
<div>
{
loggedUser && <h1>Welcome back { loggedUser.username }</h1>
}
<div className="list">
{ listOfUsers.map(user => <div key={user.id}>{ user.name }); }
</div>
</div>
)
}
You might have noticed the set type does not provide a state: this is because a set is a state itself, so it would be redundant.
useModel for set
When used on a set, the useModel
exposes the following methods:
set(key: String, data: Object | Any)
Sets an object against the model
unset(key: String)
Removes the specified key from the set
Example
Auth.set('name', 'me');
Auth.set('age', 30);
Auth.set('birthday', {
day: 30,
month: 12
})
// this will remove the age
Auth.unset('age');
// this will only remove the month
Auth.unset('birthday.month')
get(key: String | null)
Returns the entire object if no params are passed or the specified key
Example
Auth.set('name', 'me');
Auth.set('age', 30);
Auth.set('birthday', {
day: 30,
month: 12
});
Auth.get() // return the entire object
Auth.get('age') // only returns the age
Auth.get('birthday.day') // only returns 30
Non hooks version
Although it is recommended to use the hooks based version, you can still use the old redux API with connect.
import React, { useEffect } from 'react';
import { withModels } from 'redux-storm';
import UserModel from '../models/Users';
import AuthenticationModel from '../models/Authentication';
function UserList({ Users, Authentication }) {
const loggedUser = Auth.get();
const listOfUsers = Users.all();
useEffect(() => {
Auth
.set('username', 'me1245')
.set('isLoggedIn', true)
fetch('/api/users').then(r => r.json()).then(data => {
Users.insert(data);
});
}, []);
if (User.state().fetching) { return ( <div>Loading</div> )};
return (
<div>
{
loggedUser && <h1>Welcome back { loggedUser.username }</h1>
}
<div className="list">
{ listOfUsers.map(user => <div key={user.id}>{ user.name }); }
</div>
</div>
)
}
export default connect()(withModels(UserList, UsersModel, AuthenticationModel));
withModels(Component, ...models)
Accepts the component and a list of models to use that will be passed as props. Always use this inside the connect and not the other way
Yes:
export default connect()(withModels(UserList, UsersModel, AuthenticationModel));
No:
export default withModels(connect()(UserList), UsersModel, AuthenticationModel));