State management with Redux is really nice. It's also nice with Vuex. But it's lacking in filtering. When using a Pub/Sub you can filter by "topic" or by "content" but you can easily lose track of events being fired. Well it's hybrid time!
- To manage state with a simple PubSub pattern
- To use the simplicity of Flux
- For State to return a new state (pure function)
- Message filtering can be applied without a
switchstatement (you create your own event$type) - To allow for manipulation of deeply nested state properties through use of strings
{'my[index]deeply.nests.state': 'new value'}(we're sending this to SubState to not mutate the state, but make a new copy (Flux-y)! - Maintain a small size relative to deep cloning (13kb! minified, 4kb gzipped)!
- (if using modules)
import { myInstance } from 'myFile' - Components will register one or more methods to rerender themselves using your instance (see instantiation) using
myInstance.on('STATE_UPDATED', rerender)per method - Components take UI event ("click", "focus", etc) and pass it off to a Handler/Reducer
- The Handler/Reducer figures out what should change in the state (it does not update the state directly). It also figures out if/what
$typeshould be sent to the Pub/Sub module - The Handler/Reducer will then
emitUPDATE_STATEto the Pub/Sub module - The Pub/Sub module will create a new state and will
emitSTATE_UPDATEDor the specified$typeto the Components. - The Components will digest the new State using the method(s) registered in step 2
- If you want a deep clone pass in
$deep: trueinto the state on emit. ORdefaultDeep: truein the options.
npm install substate --save- copy and paste from
index.jsinto a<script>or external js file
SubState is a class so you call it like so
myFile.js
import SubState from 'substate';
Then you instantiate it as such
export const myInstance = new SubState({options});
Substate accepts an options object as an optional parameter. These are the possible options
| Option | Desc | Default |
|---|---|---|
| name | name of the instance | 'SubStateInstance' |
| currentState | index of state to start on | 0 |
| stateStorage | array of all the states | [ ] |
| state | object containing the initial state | null |
| defaultDeep | default to deep cloning the state everytime | false |
| beforeUpdate | middleware for before state is updated. Has access to substate instance | null |
| afterUpdate | middleware for after state is updated. Has access to substate instance | null |
@paramoptional method parameter@param*required method parameter
| Method | Desc | Returns |
|---|---|---|
| getState | get a state @param* - index of state needed |
state |
| getcurrentState | get the current state | current state object |
| getProp | get a prop from current state @param* - string path to prop |
property you request |
| changeState | change the version of the state @param* - {requestedState: index of state, action: (optional name of event to emit)} |
emits action parameter event or 'STATE_CHANGED' event with the new current state |
| resetState | resets the stateStorage array to an empty array |
emits 'STATE_RESET' |
@paramoptional method parameter@param*required method parameter@param[num]order of method parameter
| Method | Desc |
|---|---|
| on | @param1* STRING of event name to listen to. @param2* FUNC handler to execute when this event you listen to happens |
| off | @param1* STRING of event name to remove handler from. @param2* FUNC to remove from the execution queue |
| emit | @param1* STRING event name @param2 object of data to pass into your handler event from 'on' method |
| Event | Desc | Returns |
|---|---|---|
| 'UPDATE_STATE' | updates the entire state with the object passed in | updated state |
| 'CHANGE_STATE' | fires changeState method above requires same @params |
emits 'STATE_CHANGED' |
note: the object of data that is passed, cannot have a key called '$type'
| Method | Event | Custom Event | Next |
|---|---|---|---|
| emit | 'UPDATE_STATE' | @param2 is an object: {$type: 'MY_CUSTOM_EVENT'} |
Will update/change state. The $type property will then be emitted so you can listen to it like SubStateInstance.on('MY_CUSTOM_EVENT', func) |
Basically to utilitze a custom event, you still need to use UPDATE_STATE but the data object needs a $type with an event name you want the State to emit when updated
Use the package substate-connect and it wires up just like redux does with React.
Currently most of us do this:
// MyComponent.js
export default connect(mapStateToProps)(MyComponent)This is done in the component file itself! That should raise a red flag. Suddenly you're making your component (the thing that should be reusable) literally mapped to the state of this app! So I have a suggestion:
Do this:
// MyComponent.js
export default MyComponent;// App specific view or where my components are used for purposeful composition
import MyComponent from './components/MyComponent';
import mySubStateInstance from './state.js';
import { connect } from 'substate-connect';
const WiredMyComponent = connect(mySubStateInstance, MapStateToProps)(MyComponent);
... inside some render function
<WiredMyComponent />- Stripping
$from all class methods - deep cloning option
- remove localstorage feature
- remove
UPDATE_CHUNK - Jest tests for pubsub module
- Jest tests for substate module
- Make compatible with NodeJS AND browser
- find smaller deep clone dependency
- utilize newer version of
object-bystring - create module support for merging different state instances,
- global hooks
- better dev instructions and console warnings/errors
- seemless compatibility with infernojs, preactjs, stenciljs
- demos demos demos