Predictable state container for JavaScript apps
``sh`
npm i @mozaikjs/core
or
`sh`
yarn add @mozaikjs/core
`js
import { types } from '@mozaikjs/core'
/*
* types.model ➜ actions() ➜ create()
*/
/**
* Step 1.
* Declare node (like reducers or models)
*/
const router = types
.model({
history: types.array(types.string),
path: types.string
})
.actions({
push({ dispatch, state }, path) {
dispatch({
path,
history: [...state().history, path]
})
},
replace({ dispatch, state }, path) {
const history = state().history
history.splice(history.length - 1, 1, path)
dispatch({
path,
history
})
}
})
/**
* Step 2.
* Create instace & set default values
*/
const routerInstance = router.create({
history: ['/'],
path: '/'
})
/**
* Step 3.
* Call action
*/
routerInstance.push('/about')
/**
* Step 5.
* Get action results
*/
console.log(routerInstance.$getState())
// ➜ { history: ['/', '/about'], path: '/about' }
/**
* Subscribe on notify
*/
routerInstance.$subscribe(({ state }) => console.log(state))
routerInstance.replace('/faq')
// ➜ { history: ['/', '/faq'], path: '/faq' }
`
> Mozaikjs use immutable state
For get actual state you get call $getState() on store instance.actions and computed
Inside you can take state() method for get actual state.
`js
import { types } from '@mozaikjs/core'
const root = types
.model({
status: types.string
})
.actions({
async load({ dispatch, state }) {
dispatch({ status: 'pending' })
console.log(state().status) // ➜ 'pending'
setTimeout(() => {
dispatch({ status: 'done' })
Promise.resolve()
})
}
})
.create({
status: 'default'
})
await root.load()
console.log(root.status) // ➜ 'default' State dont mutable
console.log(root.$getState().status) // ➜ 'done' State updated
``
> Mozaikjs like Mobx State Tree check state when you change
| Type | Example | Description |
| -------------------------------- | --------------------------------------------------- | --------------------------------------------------------- |
| Primitives |
| string | types.string | |
| number | types.number | |
| boolean | types.boolean | |
| Date | types.date | Check is Date instance |
| any | types.any | Use for only immutable data. Data do not reactive. If you use this type, mozaik can not build correct scheme for data and as a result data ceases be observable. |
| Complex type | | |
| maybe (Deprecated, use optional) | types.maybe(types.string) | Value can be empty (null or undefined) |
| optional | types.optional(types.string, defaultValue?: string) | Value can be empty (null or undefined) with default value |
| array | types.array(types.number) | Array of values |
| enumeration | types.enumeration('admin', 'moderator') | Value can be one of enums |
| custom | types.custom(types.number, (value) => value > 10) | You can write custom validator |
| model | types.model(name, props) | |
You have two ways to subscribe on notify.
- chain method before create instances
- method $subscribe on created instances
`js
const commentModel = types
.model('comment', {
isLiked: types.boolean
})
.actions({
toggleLike({ dispatch, state }) {
dispatch({
isLiked: !state().isLiked
})
}
})
const fetcher = types
.model({
comments: types.array(commentModel)
})
.subscribe(console.log) // call after change state
.create({
comments: [
{
isLiked: false
}
]
})
fetcher.$subscribe(console.log)
fetcher.comments[0].$subscribe(console.log)
fetcher.comments[0].toggleLike() // Do toggle inner state
`
`js
import { types } from '@mozaikjs/core'
const resetModel = types.model({}).actions({
reset({ dispatch, state }) {
const newState = Object.keys(state()).reduce((acc, key) => {
acc[key] = null
return acc
}, {})
dispatch(newState)
return newState
}
})
const userNode = types
.model({
name: types.maybe(types.string),
age: types.maybe(types.number)
})
.compose(resetModel)
.create({
name: 'Arthur',
age: 24
})
console.log(userNode.$getState()) // ➜ { name: 'Arthur', age: 24 }
userNode.reset()
console.log(userNode.$getState()) // ➜ { name: null, age: null }
`
`js
import { types } from '@mozaikjs/core'
const user = types
.model({
name: types.string,
lastName: types.string
})
.actions({
setName({ dispatch }, name) {
dispatch({
name
})
}
})
.computed({
fullName({ state }) {
return ${state().name} ${state().lastName}!
}
})
.create({
name: 'Arthur',
lastName: 'Test'
})
console.log(user.$getState().fullName) // ➜ Arthur Test!
`
`js
import { types } from '@mozaikjs/core'
const userModel = types.model({ name: types.string })
const routerModel = types.model({ path: types.string }).actions({ push() {} })
const rootModel = types.model({
router: routerModel,
user: userModel
})
`
You can catch errors in actions. Use .catch chain method.
When error be catch you pass error context.
_context_
`js`
{
name: string
methodName: string
error: Error
store: {
}
}
`js
import { types } from '@mozaikjs/core'
const root = types
.model({
status: types.string
})
.actions({
fetch() {
throw new Error('test error')
}
})
.catch(console.log)
.create({
status: 'done'
})
root.fetch()
`
`js
import { types } from '@mozaikjs/core'
const routerStore = types
.model('router', {
path: types.string
})
.create({
path: '/'
})
const fetcherModel = types
.model({
isLoading: types.boolean
})
.actions({
fetch({ env }, path) {
console.log(env) // ➜ { httpClient: {}, localStorage, routerStore }
console.log(path) // ➜ /users
}
})
.create(
{
isLoading: false
},
{
routerStore, // You can pass other model and they be computed
httpClient: {},
localStorage: localStorage
}
)
console.log(fetcherModel.fetch('/users'))
`
Use middleware you can control each dispatch state.
Middleware chain call everytime when you call action.
If middleware don't return value, state dont change.
TODO:
- Add async
- Pass store instance
``js
const toUpperCase = state => {
return Object.keys(state).reduce((res, key) => {
res[key] = state[key].toUpperCase()
return res
}, {})
}
const user = types
.model({
name: types.string
})
.actions({
fetchUser({ dispatch }) {
dispatch({ name: 'admin' })
}
})
.use(toUpperCase)
.use('fetchUser', toUpperCase) // Add middleware on specific action
.create({
name: ''
})
user.fetchUser()
user.$getState() // ➜ { name: 'ADMIN' }
`
`js`
const myPlugin = store => {
// call after create store
store.$subscribe(ctx => {
// call after every mutation
// ctx = { state: any, oldState: any, name: string, methodName: string }
})
}
`js
import { types } from '@mozaikjs/core'
const model = types
.model({})
.plugins(myPlugin) // Add plugins
.create({})
`
---
1. Add .named() method for modeltreeWalker
2. Make for develop plugins.model('', { prop: 10 })`
3. Add new features for work with names
4. Pass inside computed function only state without actions
5. Add type for lazy load modules of tree
6. Add hooks for adapters of React + Vue
1. Data hooks useAction + useValue
2. Network hooks useQuery + useMutation
7. Make ability to parse default props to types
8. Add great support for plugins
1. Ability to append custom methods and getters
2. Possibility integrate to model by chain methods
9. Create few plugins
1. Cache manager
2. Redo/Undo
3. Persist
4. Logger
5. Connector to Redux dev tools
10. Add new Examples
11. Make site