+ Control. | - Code. | + Scalability. | - Debugging. | + Productivity.
To Encapsulate your states inside Models and Collections to treat, access, format, and organize your data in a one and same place. 🔌
Yes, Acey works smoothly with React environment, its dev experience is the logical evolution of Redux.
On Acey there is:
- No action types. ✅
- No reducers. ✅
- No selectors. ✅
- No context. ✅
AND you can trigger your actions from wherever you want without any binding. 💥
Nice hmm? 😉
Right, so Acey enable a built-in feature auto-syncing your states with your local storage. So Acey in the back-end, use this feature by storing your state in a JSON DB 🗄️.
When your program run, all your JSON files are pulled and directly added in the state of your collection (It's 100% cached, like Redis 📚).
So yeah, it works amazing for embedded systems, CLI tools, prototypes, MVP, or any other program that can work with a full DB cached. 💨
See code
Step 1/2 - State | ./counter-model.ts
import { Model } from 'acey'
class CounterModel extends Model {
constructor(initialState: any, options: any){
super(initialState, options)
}
get = () => this.state.counter
increment = () => this.setState({counter: this.get() + 1}).save()
decrement = () => this.setState({counter: this.get() - 1}).save()
/* `save()` save the Model's state in the Acey Store */
}
/* A `connected` Model has its state connected with the Acey store */
export default new CounterModel({counter: 0}, {connected: true, key: 'counter'})
Step 2/2 - Component | ./app.tsx
import React from 'react'
import { useAcey } from 'react-acey'
import Counter from './counter-model'
const App = () => {
/* Bind the Counter Model with component. */
useAcey([ Counter ])
return (
<div>
<button onClick={Counter.decrement}>decrement</button>
{Counter.get()}
<button onClick={Counter.increment}>increment</button>
</div>
);
}
export default App;
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: any = {}, options: any){
super(initialState, options)
}
}
export class TodoCollection extends Collection {
constructor(initialState: any, options: any){
super(initialState, [TodoModel, TodoCollection], options)
}
create = (content: string) => {
todos.push({
id: uuid(),
created_at: new Date(),
content
}).store()
return todos.last()
}
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 express from 'express'
import morgan from 'morgan' //request logger
import LocalStorage from 'acey-node-store'
import todos from './todos'
const initServer = async () => {
config.setStoreEngine(new LocalStorage('./db'))
await config.done()
const server = express()
server.use(express.json());
server.use(morgan('tiny'))
return server
}
initServer().then((server) => {
console.log('Server started ')
server.post('/', (req: express.Request, res: express.Response) => {
const t = todos.create(req.body.content)
res.json(t.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/3 - 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");
/* `save()` save the Model's state in the Acey Store */
updateContent = (content) => this.setState({content}).save().store()
}
export class PostCollection extends Collection {
constructor(initialState = [], options){
super(initialState, [PostModel, PostCollection], options)
}
sortByCreationDate = () => this.orderBy(['created_at'], ['desc'])
create = (content) => {
PostList.push({
id: Math.random().toString(),
content,
created_at: new Date()
}).save().store()
}
/*
`store()` store the state in the Local Store
(i) Acey auto-sync the local store's data with
their Model/Collection when the app reload.
*/
}
Step 2/3 - Components
Styles are not present to purposely make the code shorter and more readable.
./components/add-post-input.js
import React, {useState} from 'react';
import {
View,
TextInput,
TouchableOpacity,
Text,
Dimensions
} from 'react-native';
const AddPostInput = (props) => {
const { onSubmit } = props
const [text, setText] = useState('')
const onLocalSubmit = () => {
onSubmit(text)
setText('')
}
const renderSubmitTouchable = () => (
<SubmitTouchable onPress={onLocalSubmit}>
<SubmitText>CREATE</SubmitText>
</SubmitTouchable>
)
return (
<Container>
<Input
value={text}
onChangeText={(text) => setText(text)}
multiline
/>
{renderSubmitTouchable()}
</Container>
)
}
export default AddPostInput
Styles are not present to purposely make the code shorter and more readable.
./components/post.js
import React, { useState } from 'react'
import {
View,
Text,
TextInput,
TouchableOpacity,
Dimensions
} from 'react-native';
const Post = (props) => {
const {
post,
onDelete
} = props
const [updateText, setUpdateText] = useState(post.content())
const [isUpdating, setUpdatingStatus] = useState(false)
onSubmitUpdate = () => {
post.updateContent(updateText)
setUpdatingStatus(false)
}
const renderUpdateContainer = () => (
<UpdateContainer>
<UpdateInput multiline={true} value={updateText} onChangeText={(text) => setUpdateText(text)} />
<UpdateSubmitTouchable onPress={onSubmitUpdate}>
<UpdateSubmitText>
UPDATE
</UpdateSubmitText>
</UpdateSubmitTouchable>
</UpdateContainer>
)
const renderAction = (title = '', color = '', onPress = null) => (
<ActionTouchable onPress={onPress} color={color}>
<ActionText color={color}>{title}</ActionText>
</ActionTouchable>
)
const renderActions = () => (
<ActionsWrapper>
{renderAction('Update', 'blue', () => setUpdatingStatus(true))}
{renderAction('Delete', 'red', () => onDelete(post))}
</ActionsWrapper>
)
return (
<Container>
{!isUpdating && <View>
<TopWrapper>
<DateText>{post.formatedCreationDate()}</DateText>
</TopWrapper>
<ContentText>{post.content()}</ContentText>
{renderActions()}
</View>}
{isUpdating && renderUpdateContainer()}
</Container>
)
}
export default Post
Step 3/3 - Main
Styles are not present to purposely make the code shorter and more readable.
./App.js
import React from 'react';
import {
SafeAreaView,
ScrollView,
View,
Text,
} from 'react-native';
import { config } from 'acey'
import { useAcey } from 'react-acey'
import { PostCollection } from './posts'
import Post from './src/components/post'
import AddPostInput from './src/components/add-post-input'
const PostList = new PostCollection([], {connected: true, key: 'postlist'})
config.setStoreEngine(AsyncStorage)
config.done()
const App = () => {
useAcey([ PostList ])
const onSubmit = (content) => PostList.create(content)
const onDelete = (post) => PostList.delete(post).save().store()
return (
<>
<ScrollView>
<AddPostInput onSubmit={onSubmit} />
{PostList.sortByCreationDate().map((post, index) => {
return (
<View key={index}>
<Post post={post} 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
Refer to the Next Acey wrapper documentation 💡
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