A robust, lightweight (~30kb), and portable object-oriented state manager
+ Reusability. | - Debugging. | - Code. | + Productivity.
- 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..)
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. 📡
See code
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'}
*/
See code
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>
);
}
See code
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"
See code
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}}
See code
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.
*/
Try it online HERE (full app code in one single file)
See code
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}`)
})
})
See code
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;
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:
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
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
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