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

@ttoohey/react-redux-modules

v1.0.1

Published

Yet another take on modularizing redux-connected react components intended to reduce boilerplate and give modules a structure.

Downloads

1

Readme

react-redux-modules

Yet another take on modularizing redux-connected react components intended to reduce boilerplate and give modules a structure.

Module structure

A module consists of:

  • A container
  • A reducer
  • A saga

The module's index exports actions, selectors, and types, and a React component as default. The exported component is connected to the redux store and adds the module's reducer and saga to the redux root reducer.

The createModule() function is used to perform "globalization" of actions, selectors, and types. The first argument is a "path" that is used as a prefix for action types. This sets the module's namespace so that action types defined by the module won't conflict with other modules.

// index.js
import { createModule } from '@ttoohey/react-redux-modules'
import Container from './Container'
import saga from './saga'
import reducer, * as fromReducer from './reducer'

const module = createModule('modules/counter', fromReducer, Container)
  .withSaga(saga)
  .withReducer(reducer)

export const actions = module.actions()
export const selectors = module.selectors()
export const types = module.types()
export default module.container()

The reducer follows the 'ducks' pattern, mostly. The reducer.js script exports:

  • actions
  • types
  • selectors
  • and a reducer as default

The reducer exports are all locally scoped. The module index re-exports these to a global scope by prefixing the module's 'path' to action types.

An example reducer

// reducer.js
import { createReducer } from 'redux-create-reducer'

/*
 * Action Types
 */
const SET_COUNTER = 'SET_COUNTER'
const INCREMENT = 'INCREMENT'

/* 
 * Action Creators
 */
 const incrementCounter = () => ({
   type: INCREMENT
 })

const setCounter = value => ({
  type: SET_COUNTER,
  payload: value
})

/* 
 * Selectos
 */
const getCounterValue = state => state

/* 
 * Reducers
 */
const handleSetCounter = (state, action) => action.payload

/* 
 * Exports
 */
export const types = {
  SET_COUNTER,
  INCREMENT
}

export const actions = {
  incrementCounter,
  setCounter
}

export const selectors = {
  getCounterValue
}

export const handlers = {
  [SET_COUNTER]: handleSetCounter
}

export const initialState = 0

export default createReducer(initialState, handlers)

For asynchrounous actions sagas are used. The saga.js script imports the globally scoped selectors, actions and types from the module index. The default export of saga.js must be a generator function that implements the logic for handling actions.

An example saga

// saga.js
import { put, select, takeEvery } from 'redux-saga/effects'
import { selectors, actions, types } from '.'

function* handleIncrement() {
  const value = yield select(selectors.getCounterValue)
  yield put(actions.setCounter(value + 1))
}

export default function* () {
  yield takeEvery(types.INCREMENT, handleIncrement)
}

The container imports actions and selectors from the module index.

// Container.js
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import Counter from './components/Counter'
import { actions, selectors } from '.'

const mapStateToProps = state => ({
  count: selectors.getCounterValue(state)
})

const mapDispatchToProps = dispatch => bindActionCreators({
  onIncrement: actions.incrementCounter
}, dispatch)

export default connect(mapStateToProps,mapDispatchToProps)(Counter)

To complete the example, the module shows a counter with a button to increment the count.

// components/Counter.js
import React from 'react'
const Counter = props => (
  <div>
    <div>Counter value: {props.count}</div>
    <div><button onClick={() => props.onIncrement()}>Increment</button></div>
  </div>
)
export default Counter

The module can be shown in a route.

// routes.js
import React from 'react'
import { Switch, Route } from 'react-router-dom'
import Counter from 'modules/counter'

const Routes = () => (
  <Switch>
    {/* ... other routes ... */}
    <Route path='/counter' component={Counter} />
  </Switch>
)

export default Routes

Or, by using dynamic module imports to provide code-splitting

// routes.js
import React from 'react'
import { Switch, Route } from 'react-router-dom'
import Loadable from 'react-loadable'

const CounterRoute = Loadable({
  loader: () => import('modules/counter'),
  loading: () => <div>loading..</div>
})

const Routes = () => (
  <Switch>
    {/* ... other routes ... */}
    <Route path='/counter' component={CounterRoute} />
  </Switch>
)

export default Routes

Creating the store

The createStore function creates a store with redux-saga middleware included.

// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from '@ttoohey/react-redux-modules'

const store = createStore()

ReactDOM.render((
  <Provider store={store}>
    <Router>
      <Routes />
    </Router>
  </Provider>
), document.getElementById('root'));

Additonal reducers and middleware can be included when the store is being created by passing them to the createStore function.

const client = new ApolloClient()
const store = createStore({ apollo: client.reducer() }, [ client.middleware() ])

links

Some links I came across while figuring stuff out.

  • http://www.datchley.name/scoped-selectors-for-redux-modules/
  • https://jaysoo.ca/2016/02/28/organizing-redux-application/
  • https://github.com/erikras/ducks-modular-redux