redux-mvc v1.3.8
Redux MVC is a new way to handle the redux store of your app by removing most of the boiler plate and useless redundant code. Redux MVC removes the actions, their types, and the reducers. It also removes redundant data formatting, checking, and selecting functions treating your datas. And removes a lot of line of codes from the components and containers
How does it work ?
1. Objects are replaced with Model.
Get your code safer, cleaner by centralizing everything concerning your data at the same place. Don't create another util function to sort your message list, don't create another selectors function, don't spoil your app architecture. A new kind of object to handle ? Just create a new Model.
e.g Example of class concerning the User data
class UserModel extends Model {
constructor(u = {id: 123, name: 'Paul', token: 'b615f293-225e-47cd-bae5-e37f94206bc7'} ){
super(u)
}
getID = () => this.get().id
getName = () => this.get().name
getToken = () => this.get().token
//use the run function to deeply update your models
setName = (name) => this.run((state) => state.name = name)
}
export default UserModel
2. Reducers are replaced with States
? - The State class is children of Model, so it has and works with all the Model methods.
You don't create anymore a function containing an unending switch case.
A State is a class initialized with a default state as you were used to do with reducer. In this State class, you can directly creates the method you need to interact with your state (write/read or setter/getter)
e.g: Example of State class concerning the Users data
class UserState extends State {
//Must be implemented in every state class
public new = (userState: any): UserState => new UserState(userState)
constructor(state: any){
super({
user: new UserModel(state.user)
}, 'user')
}
updateName = (name) => this.run((state) => state.user.setName(name))
getUser = () => this.get().user //return the UserModel
}
export default new UserState({user: {id: 123, name: 'Paul', token: 'b615f293-225e-47cd-bae5-e37f94206bc7'}})
3. Actions are replaced with Controllers
? - The Controller class is children of Model, so it has and works with all the Model methods. Now, You don't have to create a new type, import it in your reducer, and add another line your switch case function. You don't dispatch anymore a type and a data.
You just have to create a function checking the data, and then call the method you need from the state binded with your Controller.
e.g: Here it is.
import UserState from '../states/user'
class UserController extends Controller {
constructor(stateClass: any){
super(stateClass)
}
/*
OPTION 1.
without any middleware
*/
updateName = (newName) => {
const formatedName = formatName(newName)
if (formatedName.length < 30){
/*
This line replace dispatch.
It will basically dispatch the new state once it has been updated in your State class.
*/
return this.run((state) => state.updateName(formatedName))
}
}
/*
OPTION 2.
with redux thunk to potentially make many dispatch at the same time
or making it asynchrone
*/
updateName2 = (newName) => {
return async (dispatch: Function) => {
await new Promise(resolve => setTimeout(resolve, 200)) //this is a timeout
const formatedName = formatName(newName)
if (formatedName.length < 30){
dispatch(this.run((state: any) => state.updateName(formatedName)))
/*
//Also enable multi dispatching.
dispatch(this.run((state: any) => state.something1(random1, random2)))
dispatch(this.run((state: any) => state.something2(random3)))
*/
}
}
}
}
export default UserController(UserState)
More about the Controller class
1. Connect with components
on the component side almost nothing excepted the way mapStateToProps is handled.
Here is a component.
import React from 'react';
import { connect } from 'redux-mvc'
import UserController from '../redux/controllers/user'
const Main = (props) => {
const {
user,
updateName
} = props
return (
<div>
<span>{user.getName()}</span>
<button onClick={() => updateName( 'Brian' ) } type="button">Update Name</button>
</div>
)
}
const mapStateToProps = (state) => {
return {
/*
The extend methods is native from the Controller class.
This methods pick up in the whole redux state, the right object and then transform
it into the instancied State class binded with the Controller it is called from.
so here we call it from the user Controller, the state binded with is the User State.
So it return the User State.
...then we call the getUser method to get the User Model.
*/
user: UserController.extend(state).getUser()
}
}
/*
The list listMethods methods, native from the Controller class, just return an object with
all the methods dispatching a new state, this avoid to select them each time.
(You can manually add each method if you want to)
*/
export default connect(mapStateToProps, UserController.listMethods())(Main)
Single file get started
import React from 'react';
import ReactDOM from 'react-dom';
// import store, { history } from './redux/store';
import {
Provider,
initialize,
reduxMVCMiddleware,
applyMiddleware,
createStore,
Model,
State,
connect,
Controller
} from 'redux-mvc';
import * as serviceWorker from './serviceWorker';
//----------------- USER MODEL ----------------- //
class UserModel extends Model {
constructor(u = {id: 123, name: 'Paul', token: 'b615f293-225e-47cd-bae5-e37f94206bc7'} ){
super(u)
}
getID = () => this.get().id
getName = () => this.get().name
getToken = () => this.get().token
//use the run function to deeply update your models
setName = (name: string) => this.run((state) => state.name = name)
}
//----------------- USER STATE ----------------- //
class UserState extends State {
//Must be implemented in every state class
public new = (userState: any): UserState => new UserState(userState)
constructor(state: any){
super({
user: new UserModel(state.user)
}, 'user')
}
updateName = (name: string) => this.run((state) => state.user.setName(name))
getUser = () => this.get().user //return the UserModel
}
const UserStateInstancied = new UserState({user: {id: 123, name: 'Paul', token: 'b615f293-225e-47cd-bae5-e37f94206bc7'}})
//----------------- USER CONTROLLER ----------------- //
class UserController extends Controller {
constructor(stateClass: any){
super(stateClass)
}
updateName = (newName: string) => this.run((state: any) => state.updateName(newName))
}
const UserControllerInstancied = new UserController(UserStateInstancied)
//----------------- COMPONENT ----------------- //
const Main = (props: any) => {
const {
user,
updateName
} = props
return (
<div>
<span>{user.getName()}</span>
<button onClick={() => updateName( 'Brian' ) } type="button">Update Name</button>
</div>
)
}
const mapStateToProps = (state: any) => {
return {
/*
The extend methods is native from the Controller class.
This methods pick up in the whole redux state, the right object and then transform
it into the instancied State class binded with the Controller it is called from.
so here we call it from the user Controller, the state binded with is the User State.
So it return the User State.
...then we call the getUser method to get the User Model.
*/
user: UserControllerInstancied.extend(state).getUser()
}
}
/*
The list listMethods methods, native from the Controller class, just return an object with
all the methods dispatching a new state, this avoid to select them each time.
(You can manually add each method if you want to)
*/
const MainComponent = connect(mapStateToProps, UserControllerInstancied.listMethods(), null, {})(Main)
//----------------- STORE ----------------- //
const store = createStore(
//this is initializing all the states.
//-> the function takes 2 parameters the parameters of states in an array (here: states)
//this second parameters has only been made to integrate react-router.
initialize([UserStateInstancied.getReducerConfig()] /*, {router: connectRouter(history)}*/ ),
//redux thunk works with redux-mvc
//redux-sage doesn't work with it and redux-logger has no interest since action have been removed.
//a logger should be made and adapted with redux mvc if needed.
applyMiddleware(reduxMVCMiddleware /*, routerMiddleware(history) */)
)
//----------------- APP ----------------- //
const App = () => {
return (
<Provider store={store}>
<MainComponent />
</Provider>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();