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

react-redux-annotation

v1.0.10

Published

A plugin to easily configure your react / redux / typescript project using decorators. It works with saga/thunk or without middleware.

Downloads

7

Readme

react-redux-annotation

A plugin to easily configure your react / redux / typescript project using class and decorators. It actually work:

  • without middleware (redux sync)
  • with redux-thunk
  • with redux-saga

Soon will integrate redux-promise and redux-observable.

Why it makes your code cleaner? Because decorators manage for you:

  • reducer <-> action binding
  • react property <-> state binding
  • react method <-> action binding
  • more globally connection between react component and redux store

Install it using

npm i --save react-redux-annotation

See demo-* folder and run :

npm run watch
npm run start

You have to import one of these files:

import {   } from "react-redux-annotation/redux";
import {   } from "react-redux-annotation/thunk";
import {   } from "react-redux-annotation/saga";

Decorators

The plugin provide these decorators/functions:

  • @ReduxConnect: connect react component to the redux store
  • @BindAction: bind an action and a reducer
  • @DefaultAction: set a reducer as default if action is not recognized
  • @ConnectAction / @ConnectSaga: connect a react props methods to an action
  • @ConnectProp: connect a react props property to the global state
  • exportReducers: a function to export reducers

Using without middlware

Sample

Below a sample explaining how to use it with redux store.

State

export interface Counter {
  loading: boolean;
  value: number
}
export const initialState: Counter = {
    value: 0, loading: false
}

Actions

import * as redux from 'redux'
import * as state from './state'

//DEFINE A PAYLOAD INTERFACE USED ON REDUCER
export interface JumpPayload {
  value: number
}
export class Actions { 
  static JUMP = "JUMP";
  static ADD = "ADD"; 
  static INCREMENT = "INCREMENT"; 
  static RESET = "RESET";
  //ONLY DEFINE ACTION METHOD HAVING A PAYLOAD OR PARAMS OR LOGIC (API calls)
  static jump(  value: number) {
    return { type: Actions.JUMP, value };
  } 
} 

Reducers

import { combineReducers } from 'redux'
import { BindAction, exportReducers, DefaultAction } from "react-redux-annotation";
import { Actions, JumpPayload } from "./actions"
import { Counter } from "./state"

export class Reducers {
  @BindAction(Actions.JUMP)//YOU CAN USE ACTION PAYLOAD DEFINED IN THE ACTION FILE FOR STRING TYPING
  add(state: Counter, action: JumpPayload): Counter {
    return { ...state, value: state.value + action.value };
  } 
  @BindAction([Actions.INCREMENT, Actions.ADD])//YOU CAN BIND A REDUCER TO MULTIPLE ACTION
  incrementSuccess(state: Counter, action: any): Counter {
    return { value: state.value + 1, loading: false };
  }
  @BindAction(Actions.RESET)
  reset(state: Counter, action: any): Counter {
    return { ...state, value: 0 };
  }
  @DefaultAction()//BIND A DEFAULT REDUCER IF YOU WANT => by default it returns the current state
  initial(): Counter {
    return { value: 0, loading: false }
  }
}
//EXPORT REDUCERS - OPTIONS ARE NOT REQUIRED. useReference LET YOU COMPARE ACTION BY REFERENCE (enabling having duplicate). BY DEFAULT IT IS TRUE
export const cReducers = exportReducers<Counter>(Reducers,{useReference:false});

React component

import * as React from 'react'
import * as redux from 'redux'
import { connect } from 'react-redux'
import { Actions } from './actions'
import * as state from './state'
import { ConnectAction, ConnectProp, ReduxConnect } from "react-redux-annotation/redux";


class Props {
  myName?= "Nabil"; //DEFINE DEFAULT PARAMS OR ANY OTHER PARAMS NOT CONNECTED TO THE STORE
  @ConnectAction(Actions.INCREMENT) increment?: () => void //CONNECT THE PROPS METHOD TO AN ACTION TYPE
  @ConnectAction(Actions.RESET) reset?: () => void;
  @ConnectAction(Actions.jump) jump?: (value: number) => void;//CONNECT THE PROPS METHOD TO AN ACTION FUNCTION. YOU CAN DEFINE PARAMS OR IMPORT PAYLOAD FROM ACTION FILE
  @ConnectProp((sta: state.Counter) => sta) counter?: state.Counter;//CONNECT A PROPS PROPERTY TO THE STATE USING A SELECTOR
}

@ReduxConnect(Props) //CONNECT THE COMPONENT TO THE REDUX STORE
export class PureCounter extends React.Component<Props> {
  _onClickJump = (e: React.SyntheticEvent<HTMLButtonElement>) => { this.props.jump(4) }
  _onClickIncrement = (e: React.SyntheticEvent<HTMLButtonElement>) => { this.props.increment() }
  _onClickReset = (e: React.SyntheticEvent<HTMLButtonElement>) => { this.props.reset() }
  render() {
    const { counter } = this.props
    return <div>
      <div >
        <strong>{counter.value} - {this.props.myName}</strong><br />
        {this.props.counter.loading && <h6>Loading...</h6>}
      </div>
      <form>
        <button onClick={this._onClickIncrement}>Increment</button>
        <button onClick={this._onClickReset}>Reset</button>
        <button onClick={this._onClickJump}>Jump 4</button> 
      </form>
    </div>
  }
} 

Configure the store

import * as React from 'react'
import * as ReactDOM from 'react-dom'
import * as redux from 'redux'
import { Provider } from 'react-redux'
import * as reducers from './reducers'
import * as state from './state'
import { PureCounter } from './counter'

const store: redux.Store<state.Counter> = redux.createStore(
  reducers.cReducers,
  state.initialState
)
const Root: React.SFC<{}> = () => (
  <Provider store={store}>
    <PureCounter />
  </Provider>
)
window.addEventListener('DOMContentLoaded', () => {
  const rootEl = document.getElementById('redux-app-root')
  ReactDOM.render(<Root />, rootEl)
})

Redux thunk

The configuration with redux-thunk is more or less the same. Only action and action binding changed.

Actions

The decorator automatically send the dispatch function as first parameter:

import * as redux from 'redux'
import * as state from './state'

//DEFINE A PAYLOAD INTERFACE
export interface JumpPayload {
  value: number
}
export class Actions { 
  static JUMP = "JUMP";
  static DECREMENT = "DECREMENT";
  static INCREMENT = "INCREMENT";
  static INCREMENT_SUCCESS = "INCREMENT_SUCCESS";
  static RESET = "RESET";  
  decrement(dispatch: redux.Dispatch<state.Counter>) {
    dispatch({ type: Actions.DECREMENT }); 
  }
  //ASYNC ACTION
  increment(dispatch: redux.Dispatch<state.Counter>) {
    dispatch({ type: Actions.INCREMENT });
    setTimeout(() => {
      dispatch({ type: Actions.INCREMENT_SUCCESS });
    }, 3000)
  }
  //ACTION WITH PARAM
  jump(dispatch: redux.Dispatch<state.Counter>, value: number) {
    dispatch({ type: Actions.JUMP, value });
  }
  reset(dispatch: redux.Dispatch<state.Counter>) {
    dispatch({ type: Actions.RESET });
  }
}
export const rActions = new Actions();

React component

You bind react props to an action using the reference to the function:

import * as React from 'react'
import * as redux from 'redux'
import { connect } from 'react-redux'
import { Actions } from './actions'
import * as state from './state' 
import { ConnectAction, ConnectProp, ReduxConnect } from "react-redux-annotation/thunk";

class Props {
  myName?= "Nabil";
  @ConnectAction(Actions.add) add?: () => void
  @ConnectAction(Actions.increment) increment?: () => void
  @ConnectAction(Actions.reset) reset?: () => void;
  @ConnectAction(Actions.jump) jump?: (value: number) => void;
  @ConnectProp((sta: state.Counter) => sta) counter?: state.Counter;
}
@ReduxConnect(Props)
export class PureCounter extends React.Component<Props> {
  _onClickJump = (e: React.SyntheticEvent<HTMLButtonElement>) => { this.props.jump(4) }
  _onClickIncrement = (e: React.SyntheticEvent<HTMLButtonElement>) => { this.props.increment() }
  _onClickReset = (e: React.SyntheticEvent<HTMLButtonElement>) => { this.props.reset() }
  render() {
    const { counter } = this.props
    return <div>
      <div >
        <strong>{counter.value} - {this.props.myName}</strong><br />
        {this.props.counter.loading && <h6>Loading...</h6>}
      </div>
      <form>
        <button onClick={this._onClickIncrement}>Increment</button>
        <button onClick={this._onClickReset}>Reset</button>
        <button onClick={this._onClickJump}>Jump 4</button> 
      </form>
    </div>
  }
} 

Store

The configuration of the store looks like:

import thunk from 'redux-thunk'
import * as reducers from './reducers'
import * as state from './state' 

const store: redux.Store<state.Counter> = redux.createStore(
  reducers.cReducers,
  state.initialState,
  redux.applyMiddleware(thunk),
)
...

Redux Saga

The configuration with redux-thunk is more or less the same. Now you connect a method to a saga using @ConnectSaga

Define Saga

import * as redux from 'redux'
import * as state from './state'
import { delay } from 'redux-saga'
import { put, take, fork, cancel, cancelled, race, call } from 'redux-saga/effects'
// USE THE INTERFACE ON THE REACT COMPONENT
export interface PlayPayload {
  count: number
}
//USE THIS INTERFACE IN THE REDUCER (as params)
export interface ResetPayload {
  value: number;
  type: string;
}
export class Sagas {
  static START = "START";//DEFINE THE DISPATCH ACTIONS
  static DECREMENT = "DECREMENT";//USE IT ON REDUCER TO BIND THE REDUCER
  static PLAY = "PLAY";
  static STOP = "STOP";
  static PAUSE = "PAUSE";
  static RESUME = "RESUME";
  //USE THE PAYLOAD INTERFACE
  static * animate(payload: PlayPayload) { 
    yield delay(1000) 
    while (payload.count--) {
      yield put({ type: Sagas.DECREMENT }); 
      yield delay(750)
    } 
    yield put({ type: Sagas.STOP });
  }
  static * pause(payload: PlayPayload) {
    const { play } = yield race({
      stop: take(Sagas.STOP),
      play: take(Sagas.PLAY)
    })
    if (play) {
      yield call(Sagas.play, payload)
    }
  }
  static * play(payload: PlayPayload) { 
    const task = yield fork(Sagas.animate, payload); 
    const { pause } = yield race({
      stop: take(Sagas.STOP),
      pause: take(Sagas.PAUSE)
    })
    yield cancel(task)
    if (pause) {
      yield call(Sagas.pause, payload)
    }
  }
  //THE ENTRY POINT
  static * playFlow() {
    while (true) {
      const payload : PlayPayload= yield take(Sagas.PLAY)//GET THE PAYLOAD
      yield put({ type: Sagas.START, value: payload.count }); 
      yield call(Sagas.play, payload)
    }
  }
} 

React component

Connect the react props to the saga action / state properties:

import * as React from 'react'
import * as redux from 'redux'
import { connect } from 'react-redux'
import { PlayPayload, Sagas } from './saga'
import * as state from './state'
import { ConnectProp, ConnectSaga, ReduxConnect } from "react-redux-annotation/saga";



class Props {
  myName?= "Nabil";
  @ConnectSaga(Sagas.PLAY) play?: (payload: PlayPayload) => void //USE PAYLOAD INTERFACE
  @ConnectSaga(Sagas.PAUSE) pause?: () => void //CONNECT THE SAGA ACTION TO BE DISPATCHED
  @ConnectSaga(Sagas.STOP) stop?: () => void //USE CLEAN METHOD TO DISPATCH SAGA ACTION
  @ConnectProp((sta: state.Countdown) => {  return sta; }) counter?: state.Countdown;//BIND TO THE STATE USING SELECTOR
}

@ReduxConnect(Props)
export class PureCounter extends React.Component<Props> {
  _onPlay = (e: React.SyntheticEvent<HTMLButtonElement>) => { this.props.play({ count: 10 })  }
  _onPause = (e: React.SyntheticEvent<HTMLButtonElement>) => { this.props.pause() }
  _onStop = (e: React.SyntheticEvent<HTMLButtonElement>) => { this.props.stop() }
  render() {
    const { counter } = this.props
    return <div>
      <div >
        <strong>{counter.value} - {this.props.myName}</strong><br />
        {this.props.counter.playing == "play" && < h6 > Playing...</h6>}
      </div>
      <form>
        {this.props.counter.playing != "play" && <button onClick={this._onPlay}>Play</button>}
        {this.props.counter.playing == "play" && <button onClick={this._onPause}>Pause</button>}
        <button onClick={this._onStop}>Stop</button> 
      </form>
    </div >
  }
} 

Credits

Credits to https://rjz.github.io/typescript-react-redux/ for the demo-* folder. Demos are based on this example because it is clean and simple to understand.

TODO

  • add middlewares: promise, observable
  • add react-navigation
  • add type checking