1.2.1 • Published 4 years ago
redux-class-implementation v1.2.1
redux-class-implementation
An implementation of redux in typescript with classes instead of plain objects.
Class implementation to :
- avoid selectors
- avoid repeating function in components to format datas.
- avoid redundant checking, selectors, etc... Everything is centralized and never repeated twice.
- enable the ability to remove a significant amount of code from component/container side.
- remove action types
- remove reducer
Full example with react-native here
Models
Before
const Todo {
id: string;
content: string
created_at: Date;
}
Now
import { State, Collection } from "redux-class-implementation"
interface TodoParameters {
id: string;
content: string
created_at: Date;
}
class Todo extends State {
constructor(todo: TodoParameters){
super(todo)
};
//Getters : public
public ID = (): string => this.Get().id
public Content = (): string => this.Get().content
public CreatedAt = (): Date => this.Get().created_at
}
class TodoList extends Collection {
constructor(list: Todo[] = []){
super(list, Todo)
}
SortByContent = (type: sortType = 'asc') => new TodoList((_.orderBy(this.ToPlain(), ['content'], [type])))
SortByCreateDate = (type: sortType = 'asc') => new TodoList(_.orderBy(this.ToPlain(), ['created_at'], [type]))
}
Actions
Before
import Todo from '../../models'
export const CREATE_TODO = 'CREATE_TODO'
export const CreateTodo = (todo: Todo) => {
return {
type: CREATE_TODO,
payload: todo
}
}
Now
import { Action } from 'redux-class-implementation'
import TodoState, { CONFIG } from '../states/todo'
import Todo from '../../models'
class TodoAction extends Action {
constructor(stateClass: any){
super(stateClass, CONFIG.STORE_KEY)
}
//The Exec function is a method from the Action class.
//It requires a function in parameter inside which you can run what you need
//from the state linked with this action class. (handle to avoid action types)
CreateTodo = (todo: Todo) => this.Exec((state: any) => state.AddTodo(t))
}
export default new TodoAction(TodoState)
Reducer
Before
import { CREATE_TODO } from '../actions/todo'
import { Todo } from '../../models'
interface IStateObject {
todolist: Todo[]
}
export default (state: any = DEFAULT_STATE, action: any): IStateObject => {
const {type, payload} = action
switch (type){
case CREATE_TODO:
const todolist = state.todo.slice()
todolist.push(payload)
return { ...state, todolist }
default:
return state
}
}
const DEFAULT_STATE: IStateObject = {
todolist: []
}
Now
none
State
Before
none
Now
import { State } from 'redux-class-implementation'
import { Todo, TodoList, TodoParameters } from '../../models'
interface IStateObject {
todolist: TodoParameters[]
}
export default class TodoState extends State {
constructor(initial: IStateObject = CONFIG.DEFAULT_STATE){
super({
todolist: new TodoList(initial.todolist)
})
}
//The exec function has the same working system than the one Action
//but this time, it updates the state, and return it, when done.
public AddTodo = (todo: Todo) => this.Exec((state) => state.todolist.Post(todo))
public GetTodoList = (): TodoList => this.Get().todolist
}
//This config variable has to be in each state class file
//It allows to remove the reducer parts.
export const CONFIG = {
STORE_KEY: 'todo',
DEFAULT_STATE: {
todolist: []
}
}
Store
Before
import { createStore, applyMiddleware, combineReducers } from 'redux'
import todoReducer from '../reducers/todo'
const createReducer = (): object => {
return {
todo: todoReducer
}
}
export default (): any => createStore(
combineReducers(createReducer()),
applyMiddleware(promise)
)
Now
import { createStore, applyMiddleware } from 'redux'
import { initialize, prevStateHandlerMiddleware } from 'redux-class-implementation'
import promise from 'redux-promise'
import { CONFIG as TODO_CONFIG } from './states/todo'
export default (): any => createStore(
initialize([ TODO_CONFIG ]),
applyMiddleware(promise, prevStateHandlerMiddleware) //this last middleware is used to save the previous state each time a new one is injected.
)
Connect
Before
import { CreateTodo } from '../../redux/actions/todo'
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
const { todolist } = state.todo
return {
todolist
}
}
export default connect(mapStateToProps, {
createTodo: CreateTodo,
})(Connect)
Now
import TodoAction from '../../redux/actions/todo'
import { connect } from 'redux-class-implementation'
const stateToProps = {
//always pick up the state you want using the extend method from Action that will
//transform a state plain object into a State Class
todolist: (state) => TodoAction.Extend(state).GetTodoList()
}
export default connect(stateToProps, {
createTodo: TodoAction.CreateTodo,
})(TodoList)
Component : Todo
Before
const render = () => {
const {id, created_at, content} = this.props.todo
return (
<div>
<span>{id}</span>
<span>{created_at}</span>
<span>{content}</span>
</div>
)
}
Now
const render = () => {
const {todo} = this.props
return (
<div>
<span>{todo.ID()}</span>
<span>{todo.CreatedAt()}</span>
<span>{todo.Content()}</span>
</div>
)
}
Component : Todolist
Before
import _ from 'lodash'
getListSortedByContent = () => {
const { todolist } = this.props
return _.orderBy(todolist.slice(), ['content'], ['asc'])
}
getListSortedByCreateDate = () => {
const { todolist } = this.props
return _.orderBy(todolist.slice(), ['created_at'], ['desc'])
}
const getList = () => {
return shouldSort() ?
this.getListSortedByContent()
:
this.getListSortedByCreateDate()
}
return (
<div>
{getList().map((todo, index) => {
return <Todo todo={todo} key={index} />
})}
</div>
)
Now
const getList = () => {
const { todolist } = this.props
return shouldSort() ?
todolist.SortByContent('asc').Get()
:
todolist.SortByCreateDate('desc').Get()
}
return (
<div>
{getList().map((todo, index) => {
return <Todo todo={todo} key={index} />
})}
</div>
)