acey
v1.6.3
Published
### Acey aims to be for States what React is for Components - Easily **code decoupled** and **reusable** states for JS applications. - **Lightweight** but **highly customisable** so there is no need for external librairies/tools when handling states - Rob
Downloads
44
Readme
A robust, lightweight (~30kb), and portable object-oriented state manager
Object Oriented State Manager tailored for React {Native} ⚡
Acey aims to be for States what React is for Components
- Easily code decoupled and reusable states for JS applications.
- Lightweight but highly customisable so there is no need for external librairies/tools when handling states
- Robust by a boilerplate free and class oriented architecture, so debugging is over. (no selectors, reducers, context, bindings, etc..)
Why Acey exists ?
As with most libraries, it started with the tiredness of repetitions. 💡
In December 2019, I was starting an umpteenth React-Native application and found myself coding the same states that I previously did in other apps. Reusability of components was easy with React, but I couldn't find any existing state manager that would make state reusability cool but that also combine oriented object programming and smooth management of cached data and local store. 📱
These were the 2 most important requirements for Acey:
- States are built the same way you create Components and re-use them in each of your projects. 🖥️
- Persistant so the states can be automatically synchronized with the local store and make your apps easily buildable in a no-network environment. 📡
Ways
Synchronise with local store
class User extends Model {
constructor(state: any, options: any) {
super(state, options);
}
}
const myuser = new User({ id: 1, status: 'normal' }, {connected: true, key: 'user'});
myuser.setState({ status: 'good' })
//local store object: {}
myuser.setState({ status: 'great' }).store()
//local store object: {user: {id: 1, status: 'great'}}
myuser.setState({ status: 'perfect' }).store()
//local store object: {user: {id: 1, status: 'perfect'}}
/*
Now when refreshing the page, the default state of my myuser will be: {id: 1, status: 'perfect'}
*/
Rendering on order
class User extends Model {
constructor(state: any, options: any) {
super(state, options);
}
}
const myuser = new User({ id: 1, status: 'normal' });
myuser.setState({ status: 'good' }); //doesn't re-render components
myuser.setState({ status: 'great' }); //still doesn't re-render components
myuser.setState({ status: 'perfect' }).save(); //now it re-render components because the state's change has been saved
export default function App() {
useAcey([ myuser ])
/* state changed 3 times, but re-rendered once only */
return (
<div>
<h1>{myuser.state.status}</h1>
</div>
);
}
Subscribe
class User extends Model {
constructor(state: any, options: any) {
super(state, options);
}
}
const myuser = new User({ id: 1, status: 'normal' });
myuser.watch().state((prev, after) => alert(prev.status + after.status));
myuser.setState({ status: 'good' });
// alert: "normal good"
myuser.setState({ status: 'great' });
// alert: "good great"
Nested models
class Device extends Model {
constructor(state: any, options: any) {
super(state, options);
}
}
class User extends Model {
constructor(state: any, options: any) {
super(state, options);
this.setState({
/* kids(): makes inherit the parent options */
device: new Device(state.device, this.kids()),
});
}
device = () => this.state.device;
}
const myuser = new User({
id: 1,
status: 'great',
device: {
platform: 'ios',
version: 125,
}
});
console.log(myuser.to().string()); // {"id":1,"status":"great","device":{"platform":"ios","version":125}}
myuser.device().setState({ platform: 'android', version: 200 });
console.log(myuser.to().string()); // {"id":1,"status":"great","device":{"platform":"android","version":200}}
Amazing for lists
class User extends Model {
constructor(state: any, options: any) {
super(state, options);
}
}
class UserList extends Collection {
constructor(state: any, options: any) {
super(state, [User, UserList], options);
}
}
const users = new UserList([], {});
users.append([
{ id: 1, name: 'bob' },
{ id: 2, name: 'alice' },
]);
//users = [{"id":1,"name":"bob"},{"id":2,"name":"alice"}]
const alice = users.find({ name: 'alice' });
//alice = {"id":2,"name":"alice"}
users.delete(alice);
//users = [{"id":1,"name":"bob"}]
users.push({ id: 3, name: 'mike' });
//users = [{"id":1,"name":"bob"},{"id":3,"name":"mike"}]
console.log(users.orderBy('id', 'desc'));
//print [{"id":3,"name":"mike"}, {"id":1,"name":"bob"}]
/*
...
and 40 more methods to ease your life.
*/
Quick implementations
1. Designless todolist in ReactJS
Try it online HERE (full app code in one single file)
2. A RESTful NodeJS API
Step 1/2 - State | ./todos.ts
import { Model, Collection } from 'acey'
import { v4 as uuid } from 'uuid'
export class TodoModel extends Model {
constructor(initialState = {}, options){
super(initialState, options)
}
}
export class TodoCollection extends Collection {
constructor(initialState = [], options){
super(initialState, [TodoModel, TodoCollection], options)
}
orderByLastCreation = () => this.orderBy(['created_at'], ['desc'])
}
export default new TodoCollection([], {connected: true, key: 'todolist'})
Step 2/2 - Server | ./index.ts
import { config } from 'acey'
import LocalStorage from 'acey-node-store'
import todos from './todos'
const initServer = async () => {
config.setStoreEngine(new LocalStorage('./db'))
await config.done()
return express()
}
initServer().then((server) => {
console.log('Server started ')
server.post('/', (req: express.Request, res: express.Response) => {
todos.push({ id: uuid(), created_at: new Date(), content: req.body.content }).store()
res.json(todos.last().to().plain())
})
server.delete('/:id', (req: express.Request, res: express.Response) => {
todos.deleteBy({'id': req.params.id}).store()
res.sendStatus(200)
})
server.get('/', (req: express.Request, res: express.Response) => {
res.json(todos.orderByLastCreation().to().plain())
})
server.get('/:id', (req: express.Request, res: express.Response) => {
const t = todos.find({id: req.params.id})
t ? res.json(t.to().plain()) : res.sendStatus(404)
})
server.listen(PORT, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${PORT}`)
})
})
3. A React-Native micro-blogging app with Expi
Step 1/2 - State | ./post.ts
import { Model, Collection } from 'acey'
import moment from 'moment'
export class PostModel extends Model {
constructor(initialState = {}, options){
super(initialState, options)
}
ID = () => this.state.id
content = () => this.state.content
createdAt = () => this.state.created_at
formatedCreationDate = () => moment(this.createdAt()).format("MMM Do");
}
export class PostCollection extends Collection {
constructor(initialState = [], options){
super(initialState, [PostModel, PostCollection], options)
}
sortByCreationDate = () => this.orderBy(['created_at'], ['desc'])
}
export default new PostCollection([], {connected: true, key: 'postlist'})
Step 2/2 - App
./App.js
//React imports
...
import { config } from 'acey'
import { useAcey } from 'react-acey'
import { posts } from './posts'
import Post from './src/components/post'
import AddPostInput from './src/components/add-post-input'
const App = () => {
useAcey([ posts ])
/*
save() method set the change state as done and re-render the required components
store() save the new state in the local storage
*/
const onSubmit = (content) => posts.push({id: randomID(), created_at: new Date(), content: content}).save().store()
const onDelete = (post) => posts.delete(post).save().store()
return (
<>
<ScrollView>
<AddPostInput onSubmit={onSubmit} />
{posts.sortByCreationDate().map((post) => {
return (
<View key={post.ID()}>
<Post
content={post.content()}
date={post.formatedCreationDate())}
onDelete={onDelete}
/>
</View>
)
})}
</ScrollView>
</>
);
};
export default App;
Get Started
Usage
yarn add acey
To start the Acey engine, you need to declare the configuration as done at the root of your application. Here's how, according to your environment:
ReactJS
import { config } from 'acey' //HERE
config.done() //HERE
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
make sure to install react-acey to bind your React components with your Models and Collections.
yarn add react-acey
React-Native
At the root of your app, bind the React Native Store Engine (AsyncStorage) with Acey to benefit Acey's key features.
import AsyncStorage from '@react-native-community/async-storage'
import { config } from 'acey'
config.setStoreEngine(AsyncStorage)
config.done()
make sure to install and link async-storage .
yarn add @react-native-community/async-storage
NodeJS
After all your collections have been instanced:
- bind the Acey Store Engine for Node with acey-node-store
- And set the config as done.
import NodeStorage from 'acey-node-store'
import { config } from 'acey'
import MyCollection1 from './my-collection-1'
import MyCollection2 from './my-collection-2'
...
const myCollect1 = new MyCollection1([], {connected: true, key: 'collection-1'})
const myCollect2 = new MyCollection2([], {connected: true, key: 'collection-2'})
...
config.setStoreEngine(NodeStorage)
config.done()
make sure to install acey-node-store .
yarn add acey-node-store