npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

conventional-redux

v1.0.4

Published

Redux with conventions!

Downloads

65

Readme

Conventional-redux.js NPM Build Status Coverage Status Code Climate

Conventional-redux.js is a library for small and medium applications, it wraps the react-redux and provides API based on convention over configuration pattern. It is NOT new flux implementation so everything is in 100% compatible with standard redux approach.

The idea

  1. Remove boilerplate code by adding conventions
  2. Don't break any redux rule/idea
  3. Handle basic stuff automatically with ability to override

The difference (counter example)

Standard redux module

// ------------------------------------
// Constants
// ------------------------------------
export const COUNTER_INCREMENT = 'COUNTER_INCREMENT'

// ------------------------------------
// Actions
// ------------------------------------
export function increment (value = 1) {
  return {
    type: COUNTER_INCREMENT,
    payload: value
  }
}

export const doubleAsync = () => {
  return (dispatch, getState) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        dispatch(increment(getState().counter))
        resolve()
      }, 200)
    })
  }
}

export const actions = {
  increment,
  doubleAsync
}

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
  [COUNTER_INCREMENT]: (state, action) => state + action.payload
}

// ------------------------------------
// Reducer
// ------------------------------------
const initialState = 0
export default function counterReducer (state = initialState, action) {
  const handler = ACTION_HANDLERS[action.type]

  return handler ? handler(state, action) : state
}

Conventional-redux interactor

class CounterInteractor {
  // initial state
  defaultState() {
    return 0;
  }

  // actions:
  doubleAsync() {
    setTimeout(() => { this.dispatch('counter:double') }, 500)
  }

  // reduce methods:
  onIncrement() {
    return this.state + 1;
  }

  onDouble() {
    return this.state * 2;
  }
}

Standard redux component + container

const mapActionCreators = {
  increment: () => increment(1),
  doubleAsync
}

const mapStateToProps = (state) => ({
  counter: state.counter
})

const Counter = (props) => (
  <div>
    <h2>{props.counter}</h2>
    
    <button onClick={props.increment}>
      Increment
    </button>
    
    <button onClick={props.doubleAsync}>
      Double (Async)
    </button>
  </div>
)

export default connect(mapStateToProps, mapActionCreators)(Counter)

Conventional-redux connected component

const Counter = (props, dispatch) => (
  <div>
    <h2>{props('counter')}</h2>
    
    <button onClick={dispatch('counter:increment')}>
      Increment
    </button>
    
    <button onClick={dispatch('counter:doubleAsync')}>
      Double (Async)
    </button>
  </div>
  )
}

export default connectInteractors(Counter, ['counter']);

Standard redux approach (explicit)

  1. Define component
  2. Define actions
  3. Define reducer
  4. Define mapActionCreators
  5. Define mapStateToProps
  6. Define container
  7. Connect!

Conventional-redux approach

  1. Define component
  2. Define interactor
  3. Connect!

Remember that you can combine these two approaches! Use convetional way for simple parts of your application, but when you need more control over what is going on, pure redux should be better!

Example live application

Check out an example app with code snippets here. The code is available here.

Functionalities list

Auto define actions

The library automatically defines actions based on reduce methods. Check out live example.

class CounterInteractor {
  defaultState() {
    return 0;
  }

  // You can still define increment by your own (but not need to)!
  // increment() {
  //   console.log('test');
  // }

  onIncrement(by = 1) {
    return this.state + by;
  }

  onDouble() {
    return this.state * 2;
  }
}

// dispatch examples:
// this.dispatch('counter:increment');
// this.d('counter:increment', 10);
// this.d('counter:double');

Auto handle for promises

Automatically handling for promises resolve and reject. Check out live example.

class GithubUserdataInteractor {
  defaultState() {
    return 0;
  }

  fetch(userName) {
    // need to return promise
    return fetchResource('https://api.github.com/users/' + userName)
  }

  onFetch(userName) {
    return { loading: true }
  }

  fetchSuccess(userResponse) {
    console.log(userResponse);
  }

  onFetchSuccess(userResponse) {
    return { user: userResponse }
  }

  onFetchError(error) {
    return { error: error.message }
  }
}

Interactor external dependencies

You can define an array of external dependencies to modify interactor state after non interactor actions. Check out live example.

class ExampleInteractor {
  externalDependencies() {
    return [
      { on: ['LOGOUT'], call: 'onClear' }
    ]
  }

   onClear(logoutActionArgs) {
     return {};
  }
}

Interactor computed actions

You can define a computed actions array to call additional dispatch after specific actions. In the following example the action always fires after projects:fetch or gists:fetch. Check out live example.

class FiltersInteractor {
  computedActions() {
    return [
      { after: ['projects:fetch', 'gists:fetch'],
        dispatch: 'filters:update',
        with: ['projects.languages', 'gists.languages'] 
      }
    ]
  }
  
  onUpdate(projectLanguages, gistLanguages) {
    // it fires after gists or projects fetch with resolved args
  }
}

Static and dynamic interactors

Interactors can be static or dynamic. You cannot remove once registered static interactor. Dynamic interactors can be remvoed or replaced, the right moment to manage dynamic interactors is ROUTE_CHANGE action. A good example of static interactor would be CurrentUserInteractor, of dynamic - route based interactors like ProjectsInteractor, SettingsInteractor etc. Check out live example.

// static, somewhere before connect:
registerInteractors({
  currentUser: new CurrentUserInteractor(),
  currentLanguage: new CurrentLanguageInteractor(),
});

// dynamic
onRouteChange() {
  replaceDynamicInteractors({
    counter: new CounterInteractor()
  });
}

// dynamic setup, configuring store
setRecreateReducerFunction(() => store.replaceReducer(createReducer(store.injectedReducers)));

Connect interactors

It connects interactors (state) to a specific component. Check out live example.

class Counter extends React.Component {
  render () {
    return (
      <div>
        <h2>{this.p('counter')}</h2>
        
        <button onClick={() => this.d('counter:increment')}>
          Increment
        </button>
        
        <button onClick={() => this.d('counter:doubleAsync')}>
          Double (Async)
        </button>
      </div>
    )
  }
}

export default connectInteractors(Counter, ['counter']);
// or connect all (not recommended)
export default connectInteractors(Counter);

Installation

1. Npm install

npm install conventional-redux --save

2. Add conventional-redux middleware

import { conventionalReduxMiddleware } from 'conventional-redux';
const middleware = [conventionalReduxMiddleware, thunk, routerMiddleware(history)]

Code example: https://github.com/mjaneczek/conventional-redux-demo/blob/master/app/configureStore.js#L12

3. Set recreate reducer function (needed only if using dynamic interactors)

setRecreateReducerFunction(() => store.replaceReducer(createReducer()));

Code example: https://github.com/mjaneczek/conventional-redux-demo/blob/master/app/configureStore.js#L35

4. Replace combineReducers with conventional-redux wrapper

import { createConventionalReduxRootReducer } from 'conventional-redux';

return createConventionalReduxRootReducer({
  route: routeReducer,
}, combineReducers);

// the first parameter is a default combined reducers hash
// the second is a pure combineReducers function from redux or redux-immmutable

Code example: https://github.com/mjaneczek/conventional-redux-demo/blob/master/app/reducers.js#L20

Example application

https://mjaneczek.github.io/conventional-redux-demo/

Deprecation note

The previous version of the library 0.2.2 has been deprecated. There are many breaking changes while migrating to 1.0, please be careful while updating. The outdated code is here: https://github.com/mjaneczek/conventional-redux/tree/0.2.2-outdated

Contributing

  1. Fork it ( https://github.com/mjaneczek/conventional-redux/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request