- Notifications
You must be signed in to change notification settings - Fork0
Yupeng-li/redux-simple-state
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
redux-simple-state automatically generates the redux actions and reducers for you based on the intial state. It lets you add or remove a field in a few seconds and itsget/set API makes the redux development super easy. Instead of spending hours on maintaining actions and reducers, now you can focus on more important works.
Create the store and inject the todos state
import{ReduxManager,createState}from"redux-simple-state";// Create storeconststore=ReduxManager.createStore();constINITIAL_STATE={todos:[],visibilityFilter:"SHOW_ALL"};// Generate simple state based on the initial stateconststate=createState("todosState",INITIAL_STATE);//Injects the todos state to the state treeReduxManager.registerState(state);/* * The state tree now looks like: * { * todosState:{ * todos:[], * visibilityFilter: "SHOW_ALL" * } * } */
To read the value of visibilityFilter:
letfilter=state.visibilityFilter.get();
To change the value of visibilityFilter:
state.visibilityFilter.set("SHOW_COMPLETED");
To access the selector of visibilityFilter
letvisibilityFilterSelector=state.visibilityFilter.selector;
To insert a new todo to todos
state.todos.addItem({id:0,text:"first todo",completed:false});
To add a new field into the state tree, you can just modify the INITIAL_STATE
constINITIAL_STATE={todos:[],visibilityFilter:"SHOW_ALL",user:null};/* * The state tree now looks like: * { * todosState:{ * todos:[], * visibilityFilter: "showSHOW_ALL", * user: null * } * } */// Get valueletuserDetail=this.state.user.get();// Set valuethis.state.user.set({id:"1"});
You can find the completed example in./examples folder.
- todomvc
- todomvc-typescript
In the most situations, we use Redux as a global state store where we can save our dataglobally and share it among the app. However the cost is that we have to deal with actionsand reducers. Especially for a project with a complex state structure, maintaining theactions, reducers and constants can be very cumbersome.
We just need a place to save data, can we have a simple way to do it?
redux-simple-state is a utility to simplify the process when working with Redux-based projects.The goal of this library is to make the Redux as transparent as possible, so that you can read/writestates without knowing actions and reducers. It doesNOT change the behavior of Redux.Below is a list of highlighted features.
- Dynamically generates actions and reducers based on the initial state, which allowsyou to add a new filed to the state or change existing state in a few seconds.
- Every filed in the state tree has a default selector that you can bind to a view or usein a side-effect library such as
redux-saga. - Has a higher level
getfunction and asetfunction to read and write the valueof a field without exposing details of state store and dispatching mechanism. ReduxManagerallows you to getState or dispatch an action from anywhere withoutaccessing the Store object directlyReduxManageralso allows you to inject new state or reducer on the fly
Note:seamless-immutable isNOT supported yet.
Installredux andreselect first.
yarnaddreduxreselectyarnaddredux-simple-state
Or
npminstall--savereduxreselectnpminstall--saveredux-simple-state
Use withconnected-react-router
import{applyMiddleware,compose}from"redux";import{ReduxManager}from"redux-simple-state";import{connectRouter}from"connected-react-router";import{routerMiddleware}from"connected-react-router";exportdefaultfunctionconfigureStore(initialState={},history){constenhancers=[applyMiddleware(routerMiddleware(history))];// If Redux DevTools Extension is installed use it, otherwise use Redux composeconstcomposeEnhancers=process.env.NODE_ENV!=="production"&&typeofwindow==="object"&&window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ :compose;conststore=ReduxManager.createStore(initialState,composeEnhancers(...enhancers));ReduxManager.registerReducer("router",connectRouter(history));// ... rest of your simple states or reducers// ReduxManager.registerState(myState);return{ store};}
Use withredux-persist
import{applyMiddleware,compose}from"redux";import{ReduxManager,createState}from'redux-simple-state'import{persistStore,persistReducer}from"redux-persist";importstoragefrom"redux-persist/lib/storage";import{PersistGate}from"redux-persist/integration/react";...//create storeletpersistor=persistStore(store);constINITIAL_STATE={todos:[],visibilityFilter:"SHOW_ALL"}consttodosState=createState("todos",INITIAL_STATE)constpersistConfig={key:"todos",whitelist:["visibilityFilter","todos"], storage};// Note: The properties with the prefix underscore are StateContainer's own properties.// The underscroe is used to differentiate from those dynamically added properties.ReduxManager.registerReducer(todosState._name,persistReducer(persistConfig,todosState._reducer));...//wrap your root component with PersistGate
Withredux-simple-state, you don't have to use any side effects libraries to handle async and complex synchronous logic. Below is an example to show you how to handle side effect withredux-simple-state. Please note thatredux-simple-state is not a middleware of redux, so no extra config is required.
Highlights
- You can use
asyncfunctions to handle those asynchronous flows. No generator functions, no yields. - No callback hell.
- Easy to test
// myState.jsconstINITIAL_STATE={items:[],loading:false,requestError:null};constmyState=createState("myState",INITIAL_STATE);exportdefaultmyState;
// store.jsimport{ReduxManager,createState}from"redux-simple-state";importmyStatefrom"myState.js";conststore=ReduxManager.createStore();ReduxManager.registerState(myState);
//controller.js// Promise versionexportfunctionfetchData(someValue){myState.loading.set(true);returnmyAjaxLib.post("/someEndpoint",{data :someValue}).then(response=>{mystate.items.set(response.data);myState.loading.set(false);myState.requestError.set(null);}).catch(error=>{myState.loading.set(false);myState.requestError.set(error);});}// Async versionexportasyncfunctionfetchDataAsync(someValue){myState.loading.set(true);try{letresponse=awaitmyAjaxLib.post("/someEndpoint",{data :someValue});// let anotherResponse = await myAjaxLib.post("/anotherEndpoint");mystate.items.set(response.data);myState.loading.set(false);myState.requestError.set(null);}catch(error){myState.loading.set(false);myState.requestError.set(error);}}exportfunctionaddTodosIfAllowed(todoText){lettodos=myState.todos.get();if(todos.length<MAX_TODOS){myState.todos.addItem({text :todoText})}}}
// myComponent.jsimportReactfrom'react'import*asmyControllerfrom'controller.js'...exportconstmyComponent=({controller})=>{return<buttononClick={controller.fetchData}>Click to fetch data</button>}myComponent.defaultProps={controller:myController}
If you want to get
We generate actions and reducers for a field based on the type of its initial value. These five types are supported.
- Object
- String
- Number
- Array
- Boolean
If the default value isnull, the field is marked as an Object.
Creates a state. The state is an instance ofStateContainer. You can inject the state to the store when necessary,and use it to get or set value of a field in it.
// THe initial value can be a nested objectletstate=createState("demo",{filter:"all_completed",profile:{id:1,name:"",is_active:true}});ReduxManager.registerState(state);/* * The state tree now looks like: * { * demo:{ * filter: "all_completed", * profile:{ * id: 1, * name: "", * is_active: true * } * } * } */
Params:
- name (String): The name of the field
- initialValue (not Function): The default value of the field.
Returns:
AStateContainer instance.
The singleton instance which allows you to access store from anywhere.
Creates a Redux store that holds the complete state tree of your app.This function is similar as thecreateStorefrom Redux except that it doesn't accept default reducer. Please userReduxManager.register orReduxManger.registerStateto inject the reducers.
Params:
- preloadedState (string): The initial state.
- enhancer (Function): The store enhancer.
Returns:
- Store (Object): Same as the Redux store object.
Injects the reducer to the store using the given name. This function is useful when you want to partialy migrate your project to the redux-simple-state, or you have third-party reducer to add in, such as connected-react-route.
Params:
- name (String): The field name.
- reducer (Function): A reducing function that returns the next state tree.
Returns:
- None
Example:
import{ReduxManager}from"redux-simple-state";constinitialState={todos:[],visibilityFilter:"SHOW_ALL"};functiontodoAppReducer(state=initialState,action){switch(action.type){case"SET_VISIBILITY_FILTER":returnObject.assign({},state,{visibilityFilter:action.filter});case"ADD_TODO":returnObject.assign({},state,{todos:[ ...state.todos,{text:action.text,completed:false}]});default:returnstate;}}ReduxManager.registerReducer("todos",todoAppReducer);
Injects new state to the store.
Params:
- state (StateContainer): The state returned by
createStatefunction. The name of state will be used as the field name.
Returns:
- None
Example:
import{ReduxManager,createState}from"redux-simple-state";constinitialState={todos:[],visibilityFilter:"SHOW_ALL"};consttodosState=createState("todos",initialState);ReduxManager.registerState(todosState);// To add a todotodosState.todos.addItem({text:"Buy milk",completed:false,id:1});// To change visibilityFiltertodosState.visibilityFilter.set("SHOW_COMPLETED");
Dispatches an action to the store. Same asstore.dispatch in Redux. Please checkRedux document for more details.
Params:
- action (Object): A plain object describing the change that makes sense for your application.
Returns:
- (Object): The dispatched action (see notes).
Example:
import{ReduxManager}from"redux-simple-state";letmyAction={type:"SET_VISIBILITY_FILTER",filter:"SHOW_COMPLETED"};ReduxManager.dispatch(myAction);
Returns the current state tree of your application. Same asstore.getState in Redux. Please checkRedux document for more details.
Returns:
- (Any): The current state tree of your application.
Example:
import{ReduxManager}from"redux-simple-state";constcurrentState=ReduxManager.getState();
Returns the selected value specified by the selector function.
Params:
- selector (Func): A function that accepts state and returns the selected field value. For example,
(state)=>state.user.id;
Returns:
- (Any): value.
Example:
import{ReduxManager}from"redux-simple-state";letuserFullNameSelector=state=>`${state.user.firstName}${state.user.lastName}`;constuserFullName=ReduxManager.select(userFullNameSelector);
Reset the state tree to its initial value.
Returns:
- (Action): The reset action.
Example:
import{ReduxManager,createState}from"redux-simple-state";constinitialState={todos:[],visibilityFilter:"SHOW_ALL"};consttodosState=createState("todos",initialState);ReduxManager.registerState(todosState);// To add a todotodosState.todos.addItem({text:"Buy milk",completed:false,id:1});// To change visibilityFiltertodosState.visibilityFilter.set("SHOW_COMPLETED");ReduxManager.resetState();/* The state now is reset to default{ todos:{ todos: [], visibilityFilter: "SHOW_ALL" }}*/
A container of a state which gives you all the conveniences to operate the state. You should only create a state container viacreateState function. The function will wire the actions and reducers based on the initial value.
selector
A selector function which accepts a state object and returns the value of the field
letmyState=createState("demo",{filter:"all_completed",profile:{id:1,name:"",is_active:true}});// For the root fieldletselector=myState.selector;// For sub fieldletfilterSelector=myState.filter.selector;letprofileIdSelector=myState.profile.id.selector;
Use withreact-redux
functionmapStateToProps(state){return{profile:myState.profile.selector(state)};}
Or together withreselect
import{createStructuredSelector}from"reselect";constmapStateToProps=createStructuredSelector({profile:myState.profile.selector});
Use withredux-saga
function*handler(){yieldselect(myState.filter.selector);}
Write your own selectors
import{createSelector}from"reselect";import{SHOW_ALL,SHOW_COMPLETED,SHOW_ACTIVE}from"./constants/TodoFilters";importstatefrom"./todosState";exportconstgetVisibleTodos=createSelector([state.visibilityFilter.selector,state.todos.selector],(visibilityFilter,todos)=>{switch(visibilityFilter){caseSHOW_ALL:returntodos;caseSHOW_COMPLETED:returntodos.filter(t=>t.completed);caseSHOW_ACTIVE:returntodos.filter(t=>!t.completed);default:thrownewError("Unknown filter: "+visibilityFilter);}});exportconstgetTodoCount=createSelector([state.todos.selector],todos=>todos.length);
get()
Returns the value of the field. Please make sure you call this function only after the state is registered.
letmyState=createState("demo",{filter:"all_completed",profile:{id:1,name:"",is_active:true},books:[]});//For the root fieldletvalue=myState.get();//For sub fieldletfilter=myState.filter.get();// returns "all_completed"letprofileId=myState.profile.id.get();// returns 1letbooks=mySate.books.get();// return []
set(value)
Set the value of the field. We don't check the type of the new value before writing to the filed. Please make sureto use correct value. For example, the value should be an object if the field is an object.
letmyState=createState("demo",{filter:"all_completed",profile:{id:1,name:"",is_active:true},books:[]});myState.filter.set("show_all");myState.profile.id.set(2);myState.books.set(["Learn JavaScript"]);// for nested objectmyState.profile.set({id:2,name:"test",is_active:false});// If you just want to update the object, you can use `myState.profile.update` instead of set.// There are more details below.
resetToDefault()
Resets the value of the filed to its initial value.
Example:
import{ReduxManager,createState}from"redux-simple-state";constinitialState={todos:[],visibilityFilter:"SHOW_ALL"};consttodosState=createState("todos",initialState);ReduxManager.registerState(todosState);todosState.visibilityFilter.set("SHOW_COMPLETED");todosState.visibilityFilter.get();// return SHOW_COMPLETEDtodosState.visibilityFilter.resetToDefault();todosState.visibilityFilter.get();// return SHOW_ALL
Except the shared functions, the Object field has one more function.
update(value)
Update the object. The new value will be merged. Same as doing this:
letnewState={ ...state, ...value};
Params:
- value(Object)
state.profile.update({is_active:false});
Except the shared functions, the Array field has a few more functions to manipulate its items.
addItem(item)
Adds the news item to the end of the array.Params:
- item(Any)
Example:
...// create todosStatetodosState.todos.addItem({text:"Buy milk",completed:false,id:1})
updateItems(query, value):
Updates all matched items by the given value. If the value is an object, it will be merged to existing object.
Params:
- query (Function): Query is a function accepts an item in the array, and returns a boolean which indicates if the item is selected.
- value (Any)
Example:
...// create todosState// Mark todo 1 as completedtodosState.todos.updateItems((todo)=>todo.id===1,{completed:true});// Mark incompleted todos as completedtodosState.todos.updateItems((todo)=>!todo.completed,{completed:true});
updateAll(value)
Updates all the items in the array by the given value. If the value is an object, it will be merged to existing object.
Params:
- value (Any)
Example:
...// create todosState// Mark all todos as completedtodosState.todos.updateAll({completed:true});
deleteItems(query)
Deletes all matched items.
Params:
- query (Function): Query is a function accepts an item in the array, and returns a boolean which indicates if the item is selected.
Example:
...// create todosState// Delete all completed todostodosState.todos.deleteItems((todo)=>todo.completed);
deleteAll()
Deletes all items.
Example:
...// create todosState// Delete all todostodosState.todos.deleteAll();
About
A new way to use Redux. We handle reducers and actions for you.
Topics
Resources
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors2
Uh oh!
There was an error while loading.Please reload this page.