Skip to content

Make Redux classier with Async Lifecycle Actions that take a modern approach to redux action creation.

License

Notifications You must be signed in to change notification settings

Jackman3005/redux-act-classy

Repository files navigation

Redux Act Classy

Make Redux classier with Async Lifecycle Actions that take a modern approach to redux action creation.

Build Status Coverage Status

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 a getState: () => 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)...

About

Make Redux classier with Async Lifecycle Actions that take a modern approach to redux action creation.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published