Code Monkey home page Code Monkey logo

k-redux-factory's Introduction

k-redux-factory

Factory of Redux reducers and their associated actions and selectors.

Make your Redux code base tinier and simpler to maintain

CircleCI Coverage Status NPM Version npm bundle size Greenkeeper badge

Migrating

Hey ! If you come from an early version of k-redux-factory and want to upgrade, you can read this migration guide πŸ’Ž

Contents

Purpose

k-redux-factory creates generic reducers, actions and selectors in two lines.

import { keyValue } from 'k-redux-factory'
export default keyValue({ path: 'api', name: 'todos' })

That's it, you just exported the reducer function and now you can register it through combinerReducer in Redux.

In this example, we have a todos reducer, it has to be combined into state.api.todos.

The default key is id.

You can now use it to dispatch actions and select some datas from this reducer:

import myReducer from './myReducer'

// ...
// add a todo
dispatch(myReducer.add({ id: 1, title: 'Apply to some CFP', completed: false })

// ...
// select a todo
myReducer.get(1)(getState())

One more thing, this lib handle state immutability for you !

Click right here to see the full API.

Why

We like to write Redux code as simple as possible and use its middlewares to handle real world problems. From this point of view, our Redux code base is simpler : it's like a key/value store. But one drawback is the amount of duplicated code, each resource has its own reducers, actions and selectors.

We created this lightweight library, a factory of reducers, actions and selectors, to avoid inconsistency and painful maintainability from our growing Redux code base.

Installation

  • yarn add k-redux-factory
  • npm i k-redux-factory

Documentation

factory

You need to use the factory to get a new set of reducer/actions/selectors :

// modular factory
import { factory } from 'k-redux-factory'

// or - prebuild simple factory
import { simple } from 'k-redux-factory'

// or - prebuild simpleObject factory (DEPRECATED)
import { simpleObject } from 'k-redux-factory'

// or - prebuild keyValue factory
import { keyValue } from 'k-redux-factory'

There are multiple factories signatures, take you preferred between :

  • factory(middlewares)(options) : this is the root factory, with middlewares
  • factory(options) : this is the root factory, without middlewares
  • simple(middlewares)(options) : this is a simple factory with middlewares
  • simple(options) : this is a simple factory without middlewares
  • DEPRECATED simpleObject(middlewares)(options) : this is a simpleObject factory with middlewares
  • DEPRECATED simpleObject(options) : this is a simpleObject factory without middlewares
  • keyValue(middlewares)(options) : this is a keyValue factory with middlewares
  • keyValue(options) : this is a keyValue factory without middlewares

Parameters are :

  • middlewares (optional), contain an object with pre and post fields. Both are an array of middlewares to apply before and after the core middleware
  • options (mandatory), either a string representating the reducer name, either an object with these fields :
    • key (exists only for the keyValue type -optional-), the field used to identify your objects (id is the default value)
    • path (optional), where the reducer will be combined via combineReducer
      • if empty, the reducer will be register at the root level of the redux state
      • you can use dot notation, like api.raw: your reducer will be combined into state.api.raw.<your_reducer>
    • name (mandatory), the reducer name (for instance: todos)
      • it's used to generate actions types
      • it's used to retrieve informations from selectors
    • prefix (optional) is added to actions to avoid some collisions when there are two reducers with the same name in two distincts paths
    • type (optional) can be keyValue or simple.<type> (default is keyValue)
    • defaultData (optional), set the default data value, used by reset action and for initialisation (default is an empty object {} for simple.object and default hashmap model for keyValue - see keyValue types section)

Example:

  • this reducer will use id as key field
  • it's combined into state.api.raw
  • it's name is todos
  • have default data
import { factory } from 'k-redux-factory'

export default factory({ path: 'api.raw', name: 'todos' })

Data will be stored into state.api.raw.todos.

Types are :

  • keyValue : your state is a hashmap, useful to bind your API to Redux with the following redux state model :
{
  data: [
    [<key1>, <instance1>],
    [<key2>, <instance2>],
  ],
  initialized: true,
}
  • simple : your state is directly your <instance>. This implies that you can not set undefined.
  • DEPRECATED simpleObject : your state is directly your <instance>. This implies that you can not set undefined.

Default type is keyValue.

reducer

The previous factory returns a function which is a reducer. You just have to combine it like any other reducer :

import { createStore, combineReducers, compose, applyMiddleware } from 'redux'

// import your reducer
// (created by k-redux-factory)
import todos from './myTodosReducer'

// create your Redux store as usual
const store = createStore(
  combineReducers({
    // [other reducer]
    api: combineReducers({
      // [other reducer]
      raw: combineReducers({
        // import your reducer into api.raw
        // since we configured this path
        todos,
      }),
      // [other reducer]
    }),
    // [other reducer]
  }),
  /* your Redux middlewares */
)

export default store

Exemple keyValue with default state

import { keyValue } from 'k-redux-factory'

const defaultData = [
  {
    id: 1,
    todo: 'write README.MD',
  },
  {
    id: 2,
    todo: 'watch rick and morty season three',
  },
]

export default keyValue({ defaultData })
{
  data: [
    [1, { id: 1, todo: 'write README.MD' }],
    [2, { id: 2, todo: 'watch rick and morty season three' }],
  ],
  initialized: true,
}

actions

The factory returns a function (this is the reducer) that also contains actions and selectors as fields. Some generic actions are available. By now, it's not possible to add custom ones.

To see them go to TYPES.md.

Example, we set todos to our typed keyValue reducer:

// import your reducer
// (created by k-redux-factory)
import todos from './myTodosReducer'

// dispatch can be given by one of your middleware (redux-thunk, redux-saga, etc)
// or it can be given by react-redux for example (mapDispatchToProps)
dispatch(
  // set todos
  todos.set([
    {
      id: '1', // we set 'id' as key in the factory
      visible: true,
      label: 'My first todo',
    },
    {
      id: '2',
      visible: false,
      label: 'This todo is done',
    },
  ])
)

You can also retrieve action name like this : todos.SET

selectors

The factory returns a function (this is the reducer) that also contains actions and selectors as fields. Some generic selectors are available. By now, it's not possible to add custom ones.

To see them go to TYPES.md.

Example, we retrieve the todo with id 1:

// import your reducer
// (created by k-redux-factory)
import todos from './myTodosReducer'

// state can be given by one of your middleware (redux-thunk, redux-saga, etc)
// or it can be given by react-redux for example (mapStateToProps)
todos.get('1')(state)

helpers

signature description comment
mapAction(<mapper(action)>) create middleware and map only redux action mapper(action) is mandatory
mapState(<regex>)(<mapper(state)>) create middleware and map the state of the corresponding redux actions type by the regex
reducer(<yourReducer(action, state)>) create a middleware from a standard redux reducer
mapPayload(<regex>)(<mapper(payload)>) create middleware and map the payload of the corresponding redux actions type by the regex

Example, we create a middleware but we modify only the action :

import { factory } from 'k-redux-factory'
// import your helpers
import { mapAction } from 'k-redux-factory/helpers'

// define a function to map action
const mapper = action => ({ ...action, type: `SET_${action.type}` })
// create your reducer and transform the type of action before core middleware
export default factory({ pre: [mapAction(mapper)] })({ path: 'api.raw', name: 'todos' })

Example, we create a middleware but we modify only the state :

import { factory } from 'k-redux-factory'
// import your helpers
import { mapState } from 'k-redux-factory/helpers'

// define a function to change state
const mapper = state => ({...state, todos: 'TODO_CHANGED'})
// create your reducer and transform the state before core middleware
export default factory({ pre: [mapState(/SET>TODOS/)(mapper)] })({ path: 'api.raw', name: 'todos' })

Example, we create a middleware but we modify action and state :

import { factory } from 'k-redux-factory'
// import your helpers
import { reducer } from 'k-redux-factory/helpers'

// define a function to map state depending on the action
const mapper = (action, state) => {
  switch (action.type) {
    case 'SET_TODOS': return { todos: 'TODOS_CHANGED' }
    case 'LOG_TODOS': console.log(state)
    default: return state
  }
}
// create your reducer and transform the action and state before core middleware
export default factory({ pre: [reducer(mapper)] })({ path: 'api.raw', name: 'todos' })

Example, we create a middleware but we modify only the payload :

import { factory } from 'k-redux-factory'
// import your helpers
import { mapPayload } from 'k-redux-factory/helpers'

// define a function to map payload
const mapper = payload => payload.map(p => ({ ...p, id: `ID_${p.id}` }))
// create your reducer and transform the payload before core middleware
export default factory({ pre: [mapPayload(/SET>TODOS/)(mapper)] })({ path: 'api.raw', name: 'todos' })

About uni rakun

uni rakun is created by two passionate french developers.

Do you want to contact them ? Go to their website

Guillaume CRESPEL Fabien JUIF

k-redux-factory's People

Contributors

afaugeras avatar bpetetot avatar fabienjuif avatar frinyvonnick avatar greenkeeper[bot] avatar guillaumecrespel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

k-redux-factory's Issues

API to add custom actions to state nodes

From @EmrysMyrddin (unirakun/k-ramel#60)


It could be useful to allow users to define their own custom actions along with the ones already generated by k-redux-factory.

The actions could be then decorated with dispatch function, allowing the user to easily call it from the store

const store = createStore({
  data: simpleObject({ 
    actions: {
      load: () => ({ type: 'REQUEST_DATA' }),
      loaded: (data) => ({ type: 'DATA_LOADED', payload: { data } }),
    },
  }),
})

store.data.loaded()
store.data.load({ hello: 'world' })

Since we want to push the use of the redux-saga like API, we want to be able to dispatch very simple action like simple events. For this, our library can also help the user by generating actions for him. You just give a type name for your action and it generate the action creator for you. The action creators can take an optional payload that will be aded to the action.

const store = createStore({
  data: simpleObject({
    actions: {
      load: 'REQUEST_DATA',
      loaded: 'REQUEST_DATA',
      // Also allows to give a custom action creator implementation
      custom: () => ({ type: 'CUSTOM_ACTION' })
    },
  }),
},{
  listeners: [
    when(store.data.load.type, (action, store) => store.data.loaded({ hello: 'world' }))
    // Dispatch { type: 'DATA_LOADED', payload: { hello: 'world' } }
  ]
})

I'm not entirely sure of the API. In particular, I'm not convinced by the store.data.load.type to get the type name of the action. Perhaps by adding a toString implementation to the function ? Allowing to just do when(store.data.loaded, (...) => ...) ?

API / simpleObject / defaultValue

What do you think to add the possibility to add defaultValue with the first parameter ?

function defaultValue
simpleObject() {}
simpleObject({ defaultValue: {} }) {}
simpleObject({ my: 'object' }) {}
simpleObject('string') 'string'
simpleObject([1, 2, 3]) [1, 2, 3]

An in-range update of microbundle is breaking the build 🚨

The devDependency microbundle was updated from 0.8.1 to 0.8.2.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

microbundle is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • βœ… ci/circleci: debug: Your tests passed on CircleCI! (Details).
  • ❌ ci/circleci: lockfile: Your tests failed on CircleCI (Details).

Release Notes for 0.8.2
Commits

The new version differs by 13 commits.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Error middleware with v6.0

Hello,
I would like to migrate from 5.0.2 to 6.0 but I have this error when I define a middleware with helper mapPayload. This middleware worked with v5.0.2. Please help me :) because I would like to migrate for reduce my bundle.

Thanks.

Error :

Uncaught Error: Reducer "cities" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined

Example :

import { combineReducers } from 'redux'
import { keyValue, simple } from 'k-redux-factory'
import { mapPayload } from 'k-redux-factory/helpers'

const path = 'data.analyse'

const mapCity = city => ({
  text: city.label,
  ...city,
})
const mapCities = cities => cities.map(mapCity)

const middlewares = {
  pre: [
    mapPayload(/@@krf\/SET>DATA.ANALYSE>CITIES/)(mapCities),
  ],
}

export const cities = keyValue(middlewares)({ path, key: 'value', name: 'cities', prefix: path, defaultData: [] })
export const establishments = keyValue({ path, key: 'id', name: 'establishments', prefix: path })
export const firstActs = keyValue({ path, key: 'value', name: 'firstActs', prefix: path })
export const hasConsented = simple.bool({ path, name: 'hasConsented', defaultData: false, prefix: path })
export const hasDevisChirurgie = simple.bool({ path, name: 'hasDevisChirurgie', defaultData: false, prefix: path })
export const modeConnecte = simple.bool({ path, name: 'modeConnecte', defaultData: false, prefix: path })
export const result = simple.object({ path, name: 'result', prefix: path })
export const secondActs = keyValue({ path, key: 'value', name: 'secondActs', prefix: path })

export default combineReducers({
  cities,
  establishments,
  firstActs,
  hasConsented,
  hasDevisChirurgie,
  modeConnecte,
  result,
  secondActs,
})

Add middleware support

Our reducer should be like that (pseudo code) :

reducer = (state, action) => {
  let previous = [state, action]
  midlewares.forEach(middleware => middleware(state, action))
}

The lib will add the current reducer as a core middleware.
The user could add some of middlewares before and after core one.

With this pattern the user can change

  • the action before the core reducer (use case example: generate id #9)
  • the state (use case, add some event catching)

Remove `orderBy`

I want to remove orderBy:

  • This is not the lib purpose
  • This is not performant
  • It takes some space to the bundle
  • It uses lodash function that are pain to rewrite
  • If you call orderBy, then add this is not ordered anymore

We could maybe add it later with a post middleware in helpers.

What are your thoughs ? @guillaumecrespel @EmrysMyrddin @bpetetot

Prefix action types

From @bpetetot

To avoid having too generic actions names, you might should prefix the actions names like (and keep the upper case convention) : @trampss/SET_TODOS

Benefits of k-redux-factory selectors

Sometimes, accessing an object in the state from a container is quickier to write than using its selector.
I would like to know all the advantages of k-redux-factory selectors over the direct use the state.

add an initial state for data store

if I reset my state, state.path.data will be undefined

Maybe you could add an optional property in the factory to initalize the data :

factory()()()({ name: 'docgen', type: 'uniq', defaultData: {} })

factory signature

Presently, this is boring to have middlewares in first function because 99% of times we don't use it.
So the signature is 99% of times this :
factory(/* middleware */)('id')('a.path')('aname')

How can we simplify that but let the user :

  • have a default middleware to export (overriding trampss-redux-factory)
  • change middleware with ease between each factory.

Rename simpleObject for simpleValue

The name simpleObject is misleading. We expect that the value backed by this factory have to be an object. Perhaps we can make it more obvious by naming it simpleValue which more clearly indicate that this factory is backing any kind of value.

Ramda style curryfication

Idea from @BenoitAverty

  • Use Ramda curryfication style and let user choose between those interfaces :
    • factory(key)(state)(name)
    • factory(key, state)(name)
    • factory(key, state, name)

An in-range update of npm-run-all is breaking the build 🚨

The devDependency npm-run-all was updated from 4.1.3 to 4.1.4.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

npm-run-all is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • βœ… ci/circleci: debug: Your tests passed on CircleCI! (Details).
  • ❌ ci/circleci: lockfile: Your tests failed on CircleCI (Details).

Commits

The new version differs by 3 commits.

  • a79fbac πŸ”– 4.1.4
  • d97929c remove test in version script temporary
  • 57d72eb πŸ”₯ remove ps-tree

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Change lib name ?

From @bpetetot

The librairy name doesn't speak itself... and is very long.
Does it be better to call it with factory in the lib name, like : redux-factory or trampss-redux-factory ?

Add a generic `getBy`

reducer.getBy('a.path', [valeur1, valeur2])

todo : {
visible: false,
title: 'a',
id: 1,
}

reducer.getBy('visible', false)

combine selectors "getById" and "getDatas"

replace the selector getById([id]) and getDatas() by uniq selector get()

reducer.get() => return all datas
reducer.get(key) => return the data of key
reducer.get([key]) => return all datas of keys

remove data field from simpleObject

  • remove data from simpleObject
  • split selectors into types, in this case get will not be the same for simpleObject type and keyValue
  • I would see types/simpleObject/simpleObject.selectors.js

README.md

Write to the README that the lib handle the immutability

Add the possibility to pass an array to all `keyValue` actions

Problem

We often have to update every values in a keyValue store. But with the current API, it can lead to a huge number of dispatched actions.

Proposition

I propose to make update, addOrUpdate and replace action polymorphic on their argument, allowing to pass an instance or an array of instances.

Example

import { keyValue } from 'k-redux-factory'

const store = keyValue({ key: 'id' })

store.addOrUpdate([
  { id: 1, value: '1' },
  { id: 2, value: '2' },
  { id: 3, value: '3' },
])

Migrating guide

A new markdown that explain what you have to do to go from 5.X.X to 6.X.X

API / Method names

From @bpetetot

It would be great to have methods to get action names.
It will avoid that reducers will depends on the lib actions names (which can change in the future.

Maybe we can write something like this :

const store = factory('name')
store.add.actionType()

help the developpers to detect that there is 2 reducer with same name/prefix

Only in development builds:

  • use a global variable to register all tuples (name, prefix)
  • detect if there is a collision
  • if there is a collision, print a warning (error ?) message, something like: [k-redux-factory] You combined two reducers with same name and prefix, we think this is a bug. You should look at 'prefix' option to create your reducer(s)!

@bpetetot are you ok ?

This is related to #73 that is coming back on a regular basis

Version 6.0.0

Hey @bpetetot @EmrysMyrddin @frinyvonnick !

I would like to publish the stable release 6.0.0.
Are you ok with this release ?

The changes ares theses commits: 4224d84...master

There is a rc published with the next tag: yarn add k-redux-factory@next.
If you can test this new version on one of your own project it could be great :)

Thank to all of you.


I put a deadline to the next week so I can move on if I have no response.

remove with instance object

Right now we can remove instance from keyValue per their key.
Add the possibility to remove them per reference equality to full instance.

pseudo code:

const = remove = ([object]) => {
  if (typeof object === 'object') {
    // remove per reference equality
  } else {
    // remove per id 
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.