redux-act-classy
v3.0.0
Published
Make Redux classier with Async Lifecycle Actions that take a modern approach to redux action creation.
Downloads
2
Readme
Redux Act Classy
Make Redux classier with Async Lifecycle Actions that take a modern approach to redux action creation.
Examples
Are you a hands-on learner? Checkout this example project! React Redux example app
Docs
Documentation is auto-generated but encompasses all exported members of the library. View the documentation
Define a basic action
A basic action with a generated type
class MyAction extends Classy() {
}
A basic action with a manually specified type
class MyAction extends Classy('my-action') {
}
A basic action with some data
// Using TypeScript
class MyAction extends Classy() {
constructor(readonly data: string) { super() }
}
// Using JavaScript
class MyAction extends Classy() {
constructor(data) {
super()
this.data = data
}
}
Define an asynchronous action
class LoadJokeAction extends Classy<JokeDetails>() {
public perform = async (dispatch, getState) => {
// do something asynchronously... e.g. await fetch()
return {
setup: 'Two peanuts were walking down the street',
punchLine: 'One of them was a salted'
}
}
}
Dispatch actions
An action with data
dispatch(new MyAction('some-data'))
An async action
Asynchronous actions are not directly received by reducers. Instead, Lifecycle actions are automatically dispatched to report information on the state of asynchronous activity.
dispatch(new LoadJokeAction()) // nothing interesting here
Identify actions in your reducer function
When using TypeScript
Typescript Type Guards
allow the reducer helper functions (isAction
, beforeStart
, afterSuccess
, etc) to add type information to the action within the if
block.
// Identify basic actions
if (isAction(action, MyAction)) {
// action is a MyAction
}
// Identify asynchronous lifecycle actions
if (beforeStart(action, LoadJokeAction)) {
state = {
showSpinner: true
}
} else if (afterSuccess(action, LoadJokeAction)) {
state = {
showSpinner: false,
joke: action.successResult
}
}
When using JavaScript
switch(action.type) {
// Identify basic actions
case MyAction.TYPE:
// action is a MyAction
// Identify asynchronous lifecycle actions
case LoadJokeAction.OnStart:
state = {
showSpinner: true
}
case LoadJokeAction.OnSuccess:
state = {
showSpinner: false,
data: action.successResult.data
}
}
Set Up
Add the package to your project
yarn add redux-act-classy
# or
npm install redux-act-classy --save
Add the middleware to your redux store
In Redux you can only dispatch plain object actions. To get around this design, we need a middleware to intercept our Classy actions and convert them into plain object actions (removing all functions and leaving only data properties) before passing them along to the reducer functions.
The middleware is responsible for calling perform
on asynchronous
actions and dispatching the Lifecycle Actions.
import { buildAClassyMiddleware } from 'redux-act-classy'
// You may optionally pass an object to the build method to configure the middleware
const classyMiddleware = buildAClassyMiddleware()
const reduxStore = createStore(
combineReducers({
someReducer,
anotherReducer
}),
{},
compose(applyMiddleware(classyMiddleware))
)
Motivation
I came up with some of the ideas for this library while trudging through the action portion of the redux ecosystem.
I've found it cumbersome to deal with
- action creators
- action types
- accessing action data in reducers (esp. w/ type friendliness)
- async thunk actions that result in multiple concrete actions to record the current lifecycle stage of the async action (Start/Success/Fail/etc) for loading spinners etc
Each of these individually are not that bad, but taken as a whole I felt like there should be a simpler way to do these things.
Goals
Types are first class
With the growing popularity of typescript, it makes sense to provide a solution that improves developing in typescript as well as a javascript.
Simplistic usage
I want users to feel like this is the way actions were meant to be used.
Fully tested codebase
I work in a Test Driven Design environment, and I think it's important to bring that level of confident development to this library so that others can have confidence in using this library for their production environments.
Contributing
TODO: project setup instructions...
Currently I am doing most of this by myself. If you have thoughts, inspiration, feedback, or want to add a feature. Feel free to reach out or send a pull request!
Task List
Could add a reducer that stores all lifecycle states: OnStart, etc for each Action. e.g.
state.asyncActions[MyAction.TYPE].status
. Multiple async actions of the same type (potentially with different data) may be going at the same time... (new DeleteJokeAction({id: 5})
)Add tests to middleware
- test that functions are not passed when deconstructing {...action}
Add tests to helper functions
Bug: Fix perform return types that are arrays. only able to determine they are any[] atm
perform
takes agetState: () => T
T should be the defined top level state interface for users projects... How can we make this generic (without passing it into EACH new ClassyAction)...