Anew
A lightweight Redux framework with React/ReactNative utilities
Anew removes the boilerplate overhead Redux provides (for good reasons). In addition, Anew provides additional functionalities that are necessary in most if not all Redux applications. A basic use case for anew is enough to see the code reduction, structured architecture, and robustness that the framework provides.
Anew was built to include only that which is necessary with room made for extendability. Transitioning from an existing Redux project is seamless. Anew also provides React specific utilities for bootstrapping your application. The Redux portion of Anew could be used with any view library that Redux supports (which is any view library). Future development to Anew may include bootstrap utilities for other view libraries as well.
React Native Support? You Bet!
Installation
To install anew
directly into your project run:
npm i anew -S
for yarn users, run:
yarn add anew
Table of Contents
- Creating a Store
- Combining Stores
- Creating a Test Environment
- React Utilities
- Recommended Architecture
createStore
The createStore
utility allows you to create a Redux store without the need to define action creators, action types, thunks, or even a reducer. Simply provide the createStore
parameters and the rest is taken care of by anew.
// @return Anew Store
Parameters
name
: a unique namespace specific to the store used through out the application to access the store's state object and actions.
state
: the initial state object for the store.
persist
: a user may add persist and rehydrate a stores data by assigning the persist property with the persist config as described in the redux-persist documentation. A use may also assign the persist property a true value which will use the default object created by anew. You don't need to provide the key property for persist since the store name is assigned as the key by anew and cannot be overwritten. Anew, also, if not defined, assigns the default storage object provided by redux-persist.
reducers
: pure functions, defined under a strict namespace, that return the next state tree for a specific store. Reducers receive the store's current state tree with an optional payload list as parameters. In addition, reducers that fall under an outside namepsace can also be defined inside the store and get passed both the current and defined namespace states.
effects
: pure functions that handle operations that fall inside and outside the store. They are mainly used to handle async operations that dispatch actions upon completion. Effects receive an exposed store object with public properties.
selectors
: pure functions that return a state slice selector or a derived state selector. Derived state selectors are memoized using reselect.
enhancer
: An optional parameter to enhance the store using Redux middlewares.
reducer
: an optional property for using a user defined reducer. The createStore utility automatically generates a reducer for you to accommodate the anewStore api. A user could, however, provide their own reducer, which is merged with the generated anewStore reducer and state. This is useful for using packages that follow the standard redux api inside an anew application. A perfect example, is integrating the connected-react-router package inside an anew store.
actions
: are pure functions that return an action object ({ type, payload }
). They are mainly used for a user defined reducer that has user defined action types.
Example
// stores/counter.js const counterStore = // returns: { count: 0 }counterStore // new state tree: { count: 1 }counterStoredispatchreducers // new state tree: { count: 10 }counterStore // new state tree: { count: 11 }counterStoredispatcheffects // new state tree: { count: 13 } executes one dispatch (one re-render)counterStoredispatchbatchcounterStoredispatchbatchcounterStoredispatchbatch // new state tree: { count: 15 }counterStoredispatchcounterStoredispatchbatch
Example: With User Defined Reducer
const routerStore = // returns: { isAuthenticated: false, location: { ... } }// The state is a merge of the two states: anewStore state and routerReducer state.routerStore // new state tree: { isAuthenticated: false, location: { pathname: '/home', ... } }routerStoredispatchactions // new state tree: { isAuthenticated: true, location: { ... } }routerStoredispatchreducers
combineStores
The combineStores
utility is very similar to the combineReducer
utility provided by Redux. The only difference is it combines multiple stores rather than combining multiple reducers. This combination updates each store's api references to point to the new combined store reference. This however, does not change the store shape for each given store. Methods like getState()
still return the state shape for each store.
// @return Combnined Anew Store
Parameters
name
: a unique namespace for the newly created store combination.
stores
: an array of anew stores to combine.
enhancer
: An optional parameter to enhance the new combined store using Redux middlewares.
Example
// app/store.js const rootStore = // returns: { counter: { count: 0 } }rootStore // new state tree: { counter: { count: 1 } }rootStoredispatchreducerscounter // new state tree: { counter: { count: 10 } }rootStore // After Combination using `counterStore`// new state tree: { counter: { count: 11 } }counterStoredispatchreducers
createTestEnv
The createTestEnv
utility allows you to track a store's state tree changes. After a sequence of actions executed using the anew store's api you expect a specific state tree. Having action creators and state getters coupled together in the api makes this much easier. The utility, simply returns a function (bound to a specific store) that resets the store to initial state, when called, and returns that store object. You may also execute this function before each test using a method like beforeEach
provided by jest and other testing suites and use the store object directly.
// @return Store Creator
Parameters
store
: an anew store
Example
// test/stores/counter.test.js const newTestEnv =
Anew
- React Specific Utilities
The default import for anew includes a list of helpful react utilities for bootstrapping your application. This includes methods such as creating routes, settings the applications global store object, and mounting to DOM. The utilities make it much easier to manage a growing application.
Utilities
Note, to use utilities in react-native, import the app as follows:
import App AppNativeCore from 'anew/native' /** | ------------------------------------------------------ | React Native Utils | ------------------------------------------------------ | See each util definition below for more detail. | | The only supported utilities for `react-native`, | currently, are the following: */ AppAppApp
Connect
The connect method is an alternative to the connect method provided by react-redux.
import React from 'react'import App from 'anew' Component static { return // state mapping } static { return // dispatch mapping } { return <div /> }
Alternative
import React from 'react'import App from 'anew' Component { return <div /> }
Redux
import React from 'react'import connect from 'react-redux' Component { return <div /> } state props // import store from './path/to/root/store' // // To access selectors use root (combined) store object // store.getState.<storeName>.<selectorName>() // // import someStore from './path/to/some/store' // // Or directly use store object // someStore.getState.<selectorName>() return // state mapping dispatch return // dispatch mapping SomeComponent
Routes
Use react-navigation for react native routes.
The routes utility uses react-router-dom
and react-router-config
to define application routes. The utility adopts a cascading syntax (method chaining) to build the config passed to react-router-config
.
Route
import React from 'react' // for JSXimport App from 'anew' /** * @param { String } route path * @param { React Component } route component * @param { Object } route properties * @returns { Object } react-router-config route object */App /* * React Router Config Equivalent * [ * { * path: '/somePath', * component: SomeComponent, * name: 'My Component', * exact: true, * }, * ] */ /** | ------------------ | Cascading Route | ------------------ | A cascading route remains a sibling to the previous route | with its path being appending to the previous path. */ App /* * React Router Config Equivalent * [ * { * path: '/route1', * component: ComponentOne, * }, * { * path: '/route1/route2', * component: ComponentTwo, * }, * ] */ /** | ------------------ | Group | ------------------ | A group is a cluster of sibling routes defined under one parent. | The entire application's routes fall under one group, which could include | sub-groups as children. */ /** * The current component is a routes template component that renders * the children routes for a given route inside this template wrapper. * * @param { Object } props.route The current route config */ { return <div> <h1>Group Header</h1> <div> App </div> <div>Group Footer</div> </div> } /** * @param { String } Group Path * @param { Function } Group Route Builder * @param { Object } Optional - Group Route Properties * @param { React Component } Optional React Template Component. By default * will render childern routes with no component * wrapper. */App /* * React Router Config Equivalent * [ * { * path: '/someGroupPath', * component: GroupTemplate, * routes: [ * { * path: '/route1', * component: ComponentOne, * }, * { * path: '/route2', * component: ComponentTwo, * }, * { * path: '/route2/route2', * component: ComponentThree, * }, * ], * }, * ] */ App /* * React Router Config Equivalent * [ * { * path: '/someGroupPath', * component: GroupTemplate, * exact: true, <------------- ADDITION * routes: [ * { * path: '/route1', * component: ComponentOne, * }, * { * path: '/route2', * component: ComponentTwo, * }, * { * path: '/route2/route2', * component: ComponentThree, * }, * ], * }, * ] */
Example
// app/routes.js /** | ------------------ | Components | ------------------ */ /** | ------------------ | Routes | ------------------ */ App App
Store
The store app utility provides an anew/redux store object to the application to be accessed using the redux connect
HOC.
App
Template
The template app utility defines the applications routes template where all routes will render.
import React from 'react'import App from 'anew' Component { return <div> <div>App Header</div> <div> App </div> <div>App Footer</div> </div> } App
Final Example
// index.js /** | ------------------ | Components | ------------------ */ /** | ------------------ | Bootstrap | ------------------ */ App App /** | ------------------ | Routes | ------------------ */ // pathname => / || /signin || /loginApp App App /** | ------------------ | Entry Point | ------------------ */ /** * React * @param { String } Element Id to mount to * @param { Object } Options (Optional) */App /** * React Native * @param { Object } react-navigation Navigation Stack * @param { Object } Options (Optional) */App
Recommend File Structure
This is merely a recommendation for how you could structure your anew application. Anew is composed of all pure functions making it very flexible when it comes to how you architect your application. No need to go through the hassle of binding methods, you can rest assure, each method will always receive the expected parameters.
/stores
: contains all application stores
/stores/index.js
: combination of all application stores
// /stores/index.js /** | ------------------ | Stores | ------------------ */ /** | ------------------ | Combination | ------------------ */ name: 'rootStore' stores: someStore otherStore
/stores/someStore
: contains all someStore related files
/stores/someStore/someStore.js
: someStore creation file
// /stores/someStore/someStore.js name: 'someStore' state reducers effects
/stores/someStore/someStore.state.js
: someStore initial state and object lookups
// /stores/someStore/someStore.state.js someBoolProp: false const someBoolPropSelector = someStoresomeBoolProp
/stores/someStore/someStore.selectors.js
: someStore memoized selectors
// /stores/someStore/someStore.selectors.js const someAdvancedSelector =
/stores/someStore/someStore.reducers.js
: someStore state reducers
// /stores/someStore/someStore.reducers.js /** | ------------------ | Inner Reducers | ------------------ */ const someToggleReducer = someBoolProp: !statesomeBoolProp /** | ------------------ | Outer Reducers | ------------------ */
/stores/someStore/someStore.reducers.otherStore.js
: someStore state reducers defined under another namespace
// /stores/someStore/someStore.reducers.otherStore.js const otherReducer = someBoolProp: statesomeBoolProp || otherStateotherBoolProp
/stores/someStore/someStore.effects.js
: someStore effects
// /stores/someStore/someStore.effects.js const someEffect = { const state = const someAdvanced = effects} const someOtherEffect = { }