Giter Club home page Giter Club logo

reduxsauce's Introduction

npm module Build Status Coverage Status semantic-release

What's The Story?

Provides a few tools for working with Redux-based codebases.

Currently includes:

  1. createReducer - declutter reducers for readability and testing
  2. createTypes - DRY define your types object from a string
  3. createActions - builds your Action Types and Action Creators at the same time
  4. resettableReducer - allows your reducers to be reset

createReducer

We're all familiar with the large switch statement and noise in our reducers, and because we all know this clutter, we can use createReducer to assume and clear it up! There are a few patterns I've learned (and was taught), but let's break down the parts of a reducer first:

  1. Determining the initial state.
  2. Running
  3. Knowing when to run.
  4. Injecting into the global state tree

Initial State

Every reducer I've written has a known and expected state. And it's always an object.

const INITIAL_STATE = { name: null, age: null }

If you're using seamless-immutable, this just get's wrapped. This is optional.

const INITIAL_STATE = Immutable({ name: null, age: null })

Running

A reducer is a function. It has 2 inbound parameters and returns the new state.

export const sayHello = (state = INITIAL_STATE, action) => {
  const { age, name } = action
  return { ...state, age, name }
}

Notice the export? That's only needed if you would like to write some tests for your reducer.

Knowing When To Run

In Redux, all reducers fire in response to any action. It's up to the reducer to determine if it should run in response. This is usually driven by a switch on action.type.

This works great until you start adding a bunch of code, so, I like to break out "routing" from "running" by registering reducers.

We can use a simple object registry to map action types to our reducer functions.

import Types from './actionTypes'

export const HANDLERS = {
  [Types.SAY_HELLO]: sayHello,
  [Types.SAY_GOODBYE]: sayGoodbye
}

The export is only needed for testing. It's optional.

Default handler

Sometimes you want to add a default handler to your reducers (such as delegating actions to sub reducers). To achieve that you can use DEFAULT action type in your configuration.

import Types from './actionTypes'
import { Types as ReduxSauceTypes } from 'reduxsauce'

export const HANDLERS = {
  [Types.SAY_GOODBYE]: sayGoodbye,
  [ReduxSauceTypes.DEFAULT]: defaultHandler,
}

With code above defaultHandler will be invoked in case the action didn't match any type in the configuration.

Injecting Into The Global State Tree

I like to keep this in the root reducer. Since reducers can't access other reducers (lies -- it can, but it's complicated), my preference is to not have the reducer file have an opinion.

I like to move that decision upstream. Up to the root reducer where you use Redux's combineReducers().

So, that brings us back to reduxsauce. Here's how we handle exporting the reducer from our file:

export default createReducer(INITIAL_STATE, HANDLERS)

That's it.

Complete Example

Here's a quick full example in action.

// sampleReducer.js
import { createReducer } from 'reduxsauce'
import Types from './actionTypes'

// the initial state of this reducer
export const INITIAL_STATE = { error: false, goodies: null }

// the eagle has landed
export const success = (state = INITIAL_STATE, action) => {
  return { ...state, error: false, goodies: action.goodies }
}

// uh oh
export const failure = (state = INITIAL_STATE, action) => {
  return { ...state, error: true, goodies: null }
}

// map our action types to our reducer functions
export const HANDLERS = {
  [Types.GOODS_SUCCESS]: success,
  [Types.GOODS_FAILURE]: failure
}

export default createReducer(INITIAL_STATE, HANDLERS)

This becomes much more readable, testable, and manageable when your reducers start to grow in complexity or volume.

createTypes

Use createTypes() to create the object representing your action types. It's whitespace friendly.

// Types.js
import { createTypes } from 'reduxsauce'

export default createTypes(`
  LOGIN_REQUEST
  LOGIN_SUCCESS
  LOGIN_FAILURE

  CHANGE_PASSWORD_REQUEST
  CHANGE_PASSWORD_SUCCESS
  CHANGE_PASSWORD_FAILURE

  LOGOUT
`, { prefix: 'foo' })

The second parameter is optional.

// Types.js - the 2nd parameter is optional
import { createTypes } from 'reduxsauce'

export default createTypes(`
  LOGIN_REQUEST
  LOGIN_SUCCESS
  LOGIN_FAILURE

  CHANGE_PASSWORD_REQUEST
  CHANGE_PASSWORD_SUCCESS
  CHANGE_PASSWORD_FAILURE

  LOGOUT
`)

Options

  • prefix: prepend the string to all created types. This is handy if you're looking to namespace your actions.

createActions

Use createActions() to build yourself an object which contains Types and Creators.

import { createActions } from 'reduxsauce'

const { Types, Creators } = createActions({
  loginRequest: ['username', 'password'],
  loginSuccess: ['username'],
  loginFailure: ['error'],
  requestWithDefaultValues: { username: 'guest', password: null },
  logout: null,
  custom: (a, b) => ({ type: 'CUSTOM', total: a + b })
}, { prefix: 'foo' })

The second parameter is optional.

// 2nd parameter is optional
import { createActions } from 'reduxsauce'

const { Types, Creators } = createActions({
  loginRequest: ['username', 'password'],
  loginSuccess: ['username'],
  loginFailure: ['error'],
  requestWithDefaultValues: { username: 'guest', password: null },
  logout: null,
  custom: (a, b) => ({ type: 'CUSTOM', total: a + b })
})

The keys of the object will become keys of the Creators. They will also become the keys of the Types after being converted to SCREAMING_SNAKE_CASE.

The values will control the flavour of the action creator. When null is passed, an action creator will be made that only has the type. For example:

Creators.logout() // { type: 'LOGOUT' }

By passing an array of items, these become the parameters of the creator and are attached to the action.

Creators.loginRequest('steve', 'secret') // { type: 'LOGIN_REQUEST', username: 'steve', password: 'secret' }

By passing an object of { key: defaultValue }, default values are applied.

In this case, invoke the action by putting all parameters into an object as the first argument.

Creators.requestWithDefaultValues({
  password: '123456',
  undefinedKeyWontBeUsed: true
})
// { type: 'REQUEST_WITH_DEFAULT_VALUES', username: 'guest', password: '123456' }

Options

  • prefix: prepend the string to all created types. This is handy if you're looking to namespace your actions.

resettableReducer

Provides a "higher-order reducer" to help reset your state. Instead of adding an additional reset command to your individual reducers, you can wrap them with this.

Check it out.

import { resettableReducer } from 'reduxsauce'
import { combineReducers } from 'redux'

// some reducers you have already created
import firstReducer from './firstReducer'
import secondReducer from './secondReducer'
import thirdReducer from './thirdReducer'

// listen for the action type of 'RESET', you can change this.
const resettable = resettableReducer('RESET')

// reducers 1 & 3 will be resettable, but 2 won't.
export default combineReducers({
  first: resettable(firstReducer),
  second: secondReducer,
  third: resettable(thirdReducer)
})

Changes

Note: Latest means the latest release. This is also the latest version in npmjs.com.

Jun 22, 2021 - Latest

  • FIX Update dependencies @jkeam
  • DOCS Update readme @jkeam

Jul 29, 2020 - 1.2.0

  • FIX Update minor dependencies @jkeam
  • FIX Handle numbers in action/type names @ahwatts
  • FIX Allow creating an action without any overrides @jkeam

Jun 01, 2020 - 1.1.3

  • FIX Typescript definitions to fix createReducers @jkeam

Jan 18, 2020 - 1.1.2

  • FIX Typescript allow objects while creating actions @jkeam

Oct 23, 2019 - 1.1.1

  • FIX Upgrade dependencies @jkeam
  • FIX Add more tests @jkeam
  • DOCS Add badges @jkeam

Apr 15, 2019 - 1.1.0

  • NEW Generalize typedef @jkeam

May 10, 2018 - 1.0.0 - ๐Ÿ’ƒ

  • NEW drops redux dependency sinc we weren't using it @pewniak747

September 26, 2017 - 0.7.0

  • NEW Adds ability to have a default or fallback reducer for nesting reducers or catch-alls. @vaukalak
  • NEW Adds default values to createActions if passed an object instead of an array or function. @zhang-z
  • DOCS Fixes typos. @quajo

July 10, 2017 - 0.6.0

  • NEW Makes unbundled code available for all you tree-shakers out there. @skellock & @messense
  • FIX Corrects issue with prefixed action names. @skellock
  • FIX Upgrades dependencies. @messense

April 7, 2017 - 0.5.0

  • NEW adds resettableReducer for easier reducer uh... resetting. @skellock

December 12, 2016 - 0.4.1

  • FIX creators now get the prefix as well. @jbblanchet

December 8, 2016 - 0.4.0

  • NEW createActions and createTypes now take optional options object with prefix key. @jbblanchet & @skellock

September 8, 2016 - 0.2.0

  • NEW adds createActions for building your types & action creators. @gantman & @skellock

May 17, 2016 - 0.1.0

  • NEW adds createTypes for clean type object creation. @skellock

May 17, 2016 - 0.0.3

  • DEL removes the useless createAction function. @skellock

May 17, 2016 - 0.0.2

  • FIX removes the babel node from package.json as it was breaking stuff upstream. @skellock

May 17, 2016 - 0.0.1

  • NEW initial release. @skellock

reduxsauce's People

Contributors

ahwatts avatar anyley avatar benstepp avatar carlinisaacson avatar deldreth avatar dependabot[bot] avatar gantman avatar jbblanchet avatar jkeam avatar kevinvangelder avatar messense avatar mrnkr avatar pewniak747 avatar quajo avatar semantic-release-bot avatar skellock avatar tamsanh avatar vaukalak avatar zhang-z avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reduxsauce's Issues

Question regarding createReducer

This is not really an issue but a question. What is the motivation behind specifying action types as an array in the handlers for reducers. Is it that we can specify same handler more than once action. I checked in node and if I specify an object key as an array, it only allows me to have one entry in the array. Thanks!

export const HANDLERS = {
  [Types.SAY_HELLO]: sayHello,
  [Types.SAY_GOODBYE]: sayGoodbye
}

createReducer should die on undefined keys.

When there's a typo in constants on createReducer, an undefined key is places in the object.

CONSTANTS_FOR_TEH_WIN

but reduxsauce currently doesn't notice. Def want to make it aware. This should error instantly and throw.

How to write unit test for createReducer

import { createReducer } from 'helpers';
import {
    LOAD_TEAM_REQUEST, LOAD_TEAM_SUCCESS, LOAD_TEAM_FAILTRUE,
} from 'constants/teamActionTypes';

const Default = {}

function SET_TEAM(state, action) {
    const body = action.response.body;
    let newObj = state;
    if (body &&
        body.members &&
        state.recent_members) {
        const newMembers = body.members.filter((nm)=>!!state.recent_members.find((om)=>om !== nm.id));
        newObj.recent_members = [...state.recent_members, ...newMembers];
    }

    return Object.assign({}, state, newObj);
}

export default createReducer(Default, {
    [LOAD_TEAM_REQUEST] : state => state,
    [LOAD_TEAM_SUCCESS] : (state, action) => SET_TEAM(state, action)
});

unit test code:

import createReducer from 'schemas/team';
import {
    LOAD_TEAM_REQUEST, LOAD_TEAM_SUCCESS
} from 'constants/teamActionTypes';

describe('team reducer', () => {
    it('should return the initial state when dispatch load team request action', () => {
        const defaultState = {};
        expect(createReducer(defaultState, {LOAD_TEAM_REQUEST})).toEqual(defaultState);
    })

    it('should return the team state when diapath load team success', () => {
        expect(createReducer(defaultState, {LOAD_TEAM_SUCCESS: (defaultState, {
            test: '123'
        })})).toEqual(defaultState);
    })
})

repo createActions example error TypeScript

The default repo createActions example has some error with typescript :

export const { Types, Creators } = createActions({
  loginRequest: ['username', 'password'],
  loginSuccess: ['username'],
  loginFailure: ['error'],
  requestWithDefaultValues: { username: 'guest', password: null },
  logout: null,
  custom: (a, b) => ({ type: 'CUSTOM', total: a + b }),
});

How to trigger an action from a reducer to another reducer

Hi,

I need to be able to call an action of another reducer (UserReducer) from my current reducer (ListReducer)

ListReducer

// External Reducer
import UserReducer from './UserReducer'

const { Types, Creators } = createActions({
  filter_success: null
})


export const filterSuccess = (state, action) => {
  const parseResponse = JSON.parse(action.payload)
  console.log('filterSuccess parseResponse ===> ', parseResponse);

  if (parseResponse.data && parseResponse.data.length > 0) {
    if (parseResponse.data[0].error) {
      // Here need Call `show_external_error` inside `UserReducer`
      UserReducer.show_external_error(manageErrorMessage(parseResponse.data[0], 'Filter error.'))
    }
  }
}


export const reducer = createReducer(INITIAL_STATE, {
  [Types.FILTER_SUCCESS]: filterSuccess,
})

UserReducer

const { Types, Creators } = createActions({
  show_external_error: ['errorMessage'],
})

export const showExternalError = (state, action) => {
  // Logs Never fired
  console.log('state ===> ', state)
  console.log('showError action ==> ', action);
  
  // i'm using `Inmmutable(state)` based in this issue https://github.com/infinitered/ignite/pull/20#issuecomment-202550408
  return Immutable(state).merge({ errorMessage: action.errorMessage ? action.errorMessage : 'Something went Wrong', requesting: 0 })
}


export const reducer = createReducer(INITIAL_STATE, {
  [Types.SHOW_EXTERNAL_ERROR]: showExternalError,
})

Default handler

Sometime you should write reducers that delegate actions to nested reducers.

const ACTION_HANDLERS = {
  [Types.ONE]: oneHandler,
  [Types.TWO]: twoHandler,
  [ReduxSauce.DEFAULT]: defaultHandler,
};

And all actions (except Types.ONE and Types.TWO) should be delegated to defaultHandler.

Aliasing an import created by createActions or just import specific actions?

Given my redux file:

DeviceNotificationSubscriptionRedux.js

import { createReducer, createActions } from 'reduxsauce'

const { Types, Creators } = createActions({
  subscribeDevice: ['userId', 'authToken'],
});

export const DeviceNotificationSubscriptionTypes = Types;
export default Creators

export const success = (state) => { fetching: true }

export const reducer = createReducer({ fetching: false }, {
  [Types.SUBSCRIBE_DEVICE]: success,
});

This is what my import looks like:
import DeviceNotificationSubscriptionActions from 'DeviceNotificationSubscriptionRedux'

Is there a way to alias this import or just import SUBSCRIBE_DEVICE action?

This doesn't seem to be working:
import { subscribeDevice } from 'DeviceNotificationSubscriptionRedux'

Error in Documentation

Howdy!

Noticed a little mistake in your Complete Example section:

// map our action types to our reducer functions
export const HANDLERS = [
  [Types.GOODS_SUCCESS]: success,
  [Types.GOODS_FAILURE]: failure
]

should be an object mapping, correct?

export const HANDLERS = {
  [Types.GOODS_SUCCESS]: success,
  [Types.GOODS_FAILURE]: failure
}

We need more boilerplate

Reduxsauce is a travesty because it decreases the time it takes to write apps, therefore I charge my client less hours for development, therefore I make less money. So we need some boilerplate, please.

Error: handlers cannot have an undefined key

I have a problem to create reducer using a Handlers object.

This is my environment version:

"axios": "^0.18.0",
    "jwt-decode": "^2.2.0",
    "react": "^16.4.2",
    "react-dom": "^16.4.2",
    "react-redux": "^5.0.7",
    "react-router-dom": "^4.3.1",
    "react-scripts": "1.1.5",
    "redux": "^4.0.0",
    "redux-logger": "^3.0.6",
    "redux-saga": "^0.16.0",
    "reduxsauce": "^1.0.0"

image

Here is a project link.
Please I need a help to solve that.

https://github.com/francisrod01/redux-sagas-app

add an item into an array

Hello, I'm in trouble figuring the way redux sauce works
In seamless-immutable, they say:

The Object and Array methods that do not mutate are still available, although they return Immutable instances instead of normal arrays and objects.

The non-mutating array methods seamless-immutable supports are map, filter, slice, concat, reduce, and reduceRight, and for objects just keys.
Things may have change though
My INITIAL_STATE is displayed, and onPress the best I can have is the list to disappear, in Reactotron, the action addWord seems to work and I presume I should get 'coucou' in the list but it's blanks at best or I get filter(myreducername) returned undefined,...

Here's my INITIAL_STATE :)

export const INITIAL_STATE = Immutable({
    filters : ["bons plans","autour de moi","coiffeur","-30โ‚ฌ","femme"]
});

Here is my action in Redux

export const add = ( state, keyword) => {
   state.merge({filters: keyword})
};

Here is where I use redux, props and actions

<FlatList horizontal={true}
              data={this.props.data}
             renderItem={({item, index}) => <Chips filterWord={item}
                                                      key={item} 
                                                      onPress = {() => this.props.addWord('coucou')}/>}

Here is my class component

const mapStateToProps = state => {
  return {
      data: state.filter.filters,
   }
 };

const mapDispatchToProps = (dispatch) => {
  return {
      addWord: (keyword) => {
        dispatch(FilterActions.filterAdd(keyword))
      }
  }
};

My reducer is called filter in Redux/index.js
I can't figured out what I'm doing wrong, in the exemple ignite RN boilerplate with native base loginReduce use state.merge but I don't know why ,if it is the only way...
Thank you for any idea

Redux-thunk

How can i use redux-thunk with reduxsauce

I wonder is there any method to support redux sauce for editor code hints?

First of all, I love the redux sauce, it helps make my code elegant.
But there is a problem, I find that it's hard to use editor hint to help me input the ActionType.
For example, here is my Action type definition:

export const { Types, Creators } = createActions({
    changeTabIndex: ['newIndex'],
}, 'FEED_LIST_');

When I want to use createReducer to create real reducer in another file, I have to input FEED_LIST_CHANGE_TAB_INDEX manually:

const HANDLERS = {
    [FeedListActionTypes.FEED_LIST_CHANGE_TAB_INDEX]: changeTab,
};

export default createReducer(INITIAL_STATE, HANDLERS);

Is there any method to support redux sauce for editor code hints?

Reusable reducers

Hello and thank you for such a wonderful library!

Could you please help me to understand, is it possible at all to create one reducer that can be used in different parts of my store independently. My example is the following: I have multiple entities, in reducers I store the list of them as well as pagination state. For example:

entityA {
  data: [],
  pagination: {...}
}

entityB: {
  data: [],
  pagination: {...}
}

So I want to have reducer for pagination used in entityA be independent from entityB.

Perhaps I should change the way I combine it with entityA reducer:

const whateverPaginationHandler = (state = INITIAL_STATE, action) => ({
  ...state,
  pagination: paginationReducer(state.pagination, action),
});

Thank you in advance!

novice can't work it out.

happy holidays guys,
This design is ending up error.
If you have time, please help me sort this out.
it sounds like I am not dispatching actions right or so.

...Container...

import { questionAnswer } from '../Redux/questionsRedux';


class Screen1 extends Component {...

  onButtonPress(){
    this.props.questionAnswer('question1', value)
  }

...}

const mapStateToProps = (state) => {
  const { question1, question2, question3 } = state.questions;
  return { question1, question2, question3 };
};

export default connect(mapStateToProps, { questionAnswer })(Screen1);

redux setup must be all good below

...questionsRedux.js...

/* ------------- Types and Action Creators ------------- */

const { Types, Creators } = createActions({
  questionAnswer: ['prop', 'value']
})
export const questionsTypes = Types
export default Creators

/* ------------- Initial State ------------- */

export const INITIAL_STATE = Immutable({
  question1: null,
  question2: null,
  question3: null
})

/* ------------- Reducers ------------- */

export const update = (state = INITIAL_STATE, { prop, value }) => {
  console.log(prop + value)
  return state.merge({ ...state, [prop]: value })
}

/* ------------- Hookup Reducers To Types ------------- */

export const reducer = createReducer(INITIAL_STATE, {
  [Types.QUESTION_ANSWER]: update,
})

...combineReducers...

import { combineReducers } from 'redux'
...

/* ------------- Assemble The Reducers ------------- */
export const reducers = combineReducers({
  questions: require('./questionsRedux').reducer
})

handlers cannot have an undefined key

i updated from version 0.4.1 to latest 1.0.0 and found a breaking change that was not mentioned.

throw new Error('handlers cannot have an undefined key')

my store have a few initial states with key of undefined that later i update

ex:

export const INITIAL_STATE = Immutable({
  phoneNumber : null,
  userToken : null,
  userId:null,
  inProcess : false,
  error : null,
  loginState : LOGGED_OUT,
  codeEnterTries : 0,
  userName: '',
  locale:currentLocale(),
  allowSMS:false,
  createNewUser:true,
  isRegisteredFarmer:false,

})

after updating reduxsauce cannot compile
maybe consider reverting #17 as wanted by #64
thanx

using redux-persist

Hello, thank for this tool, however, I am using ignite 2 for my boilerplate in react native, I am using createReducer from redux sauce, whitelisted the state that I want to persist, but from what I know is I have to receive the state from my reducer in order to rehydrate the state. How do I do this when I'm using createReducers?

The example is great, but there is no part which shows the actual dispatch...

My Reducer: AppDataRedux.js

`import { createReducer, createActions } from 'reduxsauce'
import Immutable from 'seamless-immutable'

/* ------------- Types and Action Creators ------------- */

const { Types, Creators } = createActions({
updateTestString: ['myTestString'],
})

export const AppDataTypes = Types
export default Creators

/* ------------- Initial State ------------- */

export const INITIAL_STATE = Immutable({
myTestString: 'Oink',
})

/* ------------- Reducers ------------- */

// successful testString lookup HERE IS WHERE THE ERROR OCCURS
export const updateTestString = (state = INITIAL_STATE, action) => {
return { ...state, myTestString: action.myTestString }
}

/* ------------- Hookup Reducers To Types ------------- */

export const reducer = createReducer(INITIAL_STATE, {
[Types.UPDATETESTSTRING]: updateTestString,
})
`
From my component:

import {updateTestString} from '../../Redux/AppDataRedux';`

const mapStateToProps = (state) => {
return {
myTestString: state.myTestString
}
}

const mapDispatchToProps = dispatch => {
return {
updateTestString: mytest => {
dispatch(updateTestString(mytest))
}
}
}

export default connect(mapDispatchToProps, mapStateToProps)(Coll_Entry)
`
getting: undefined is not an object (evaluating 'action.myTestString')
this is on the export const updateTestString in my reducer...

Am I calling this correctly and is this the right place to ask a question?

How to use with Flow?

Is there a way to use Flow to do type checking on the parameters when using things like createActions? I tried several methods that all failed.

state.merge is not a function

Hi. Can someone please explain why I am getting state.merge is not a function when my reducer is called ??

import { createActions, createReducer } from 'reduxsauce'
import { createSelector } from 'reselect'
import Immutable from 'seamless-immutable'

const { Types, Creators } = createActions({
    loginRequest: ['username', 'password']
})

export const LoginTypes = Types;
export default Creators;

export const INITIAL_STATE = Immutable({
    user: null,
    token: null,
    refreshToken: null,
    loading: false,
    error: null,
    biometryEnabled: false,
    biometryPromptShown: false
})

export const loginRequestReducer = (state) => { console.log(state); return state.merge({
    loading: true,
    error: null
})};

export const reducer = createReducer(INITIAL_STATE, {
    [Types.LOGIN_REQUEST]: loginRequestReducer
})

When I call store.getState() elsewhere I can see the merge function.

Screenshot 2019-10-07 at 12 35 29

Is it possible to have two creators in different reducers with the same name ?

Hello,
I'm using reduxsauce for a project of mine, while trying to DRY up the code I needed two actions to have the same creator name
for example :
my store looks something like this :
state = { demands : { list, fetching, success, error }, transfers : { list, fetching, success, error } }

while I understand that Redux doesn't allow two actions to have the same name I want to have the ability to give the creators the same name like this :

//demandsRedux 
{Types, Creators} = createActions({
  datasetRequest: []
  //...
});

export const request = state => state.merge({ fetching: true, success: false, error: null });
//...

export const reducer = createReducer(INITIAL_STATE, {
  [Types.DEMANDS_DATASET_REQUEST]: request,
  //...
});
//transfersRedux 
{Types, Creators} = createActions({
  datasetRequest: []
  //...
});

export const request = state => state.merge({ fetching: true, success: false, error: null });
//...

export const reducer = createReducer(INITIAL_STATE, {
  [Types.TRANSFERS_DATASET_REQUEST]: request,
  //...
});

It would be nice to have a way to prefix action names to avoid conflicting actions

I can not get 100% tests coverage

I'm trying to test a application using Jest. I implemented the following approach:

import { createActions, createReducer } from "reduxsauce";

/* Action types & creators */
export const { Types, Creators } = createActions({
    addSample: ["text"],
    removeSample: ["id"]
});

/* Handlers */
export const INITIAL_STATE = [];
export function add(state = INITIAL_STATE, action) {
    return [...state, { id: Math.random(), text: action.text }];
}
export function remove(state = INITIAL_STATE, action) {
    return state.filter(sample => sample.id !== action.id);
}

/* Reducer */
export default createReducer(INITIAL_STATE, {
    [Types.ADD_SAMPLE]: add,
    [Types.REMOVE_SAMPLE]: remove
});

It works fine. However, in the test I can't reach the 100% coverage, because only in the criteria of Branch, it logs that coverage is 0% (on the anothers criteria I reach 100% - Statements, Functions, Lines); On feedback of the coverage logs it is stated that on lines 11 and 24 the code test isn't covering tests, on declarations of the Handlers add() and remove().

I guess internally the reduxsauce implements Reducer with some switch case, but I have no idea how I can testing this part of a way allow the test coverage criteria.

Support FSA-style actions? (with payload and meta fields)

Hi,

thanks a lot for this awesome module!

Flux Standard Actions seem to be rather standard now. And quite some libraries out there are designed around this pattern. (example: https://github.com/colinbate/redux-form-submit-saga)

I though of something like:

const { Types, Creators } = createActions({
  increment: 'value',  // first argument will become payload
  myAction: [a, b], // payload will have { a: x, b: y } scheme
  myActionFailure: 'error',  // will create an error action
}, 
{ useFSA: true }  // activate FSA behavior here (or make it default?) 
)

and then:

Creators.myAction('foo','bar') // => { type: MY_ACTION, payload: { a: 'foo', b: 'bar' } }
Creators.myActionFailure('failed') // => { type: MY_ACTION_ERROR, payload: failed, error: true }
Creators.increment(25) // => { type: INCREMENT, payload: 25 }

// additionally, an optional subsequent parameter can set a meta object 
Creators.myAction('foo','bar', { metaKey: 123 }) // => { type: MY_ACTION, payload: { a: 'foo', b: 'bar' }, meta: { metaKey: 123 } )

What do you think?

useReducer support

Hi

I think it would be really cool if we could use redux sauce with the hook method useReducer

Would this ever be possible?

Reduxsauce cannot dispatch action

I've tried on follow your doc, but it's not working. My getInterviewFormById Func is not run.

My Reducer

import { createActions, createReducer } from 'reduxsauce';

export const { Types, Creators } = createActions(
  {
    getInterviewFormById: ['interviewId'],
    getInterviewFormByIdSuccess: ['data'],
  },
  {},
);

const INITIAL_STATE = {
  interviewForm: [],
};

export const getInterviewFormByIdSuccess = (
  state = INITIAL_STATE,
  { data },
) => ({
  ...state,
  interviewForm: data,
});

export default createReducer(INITIAL_STATE, {
  [Types.GET_INTERVIEW_FORM_BY_ID_SUCCESS]: getInterviewFormByIdSuccess,
});

My sagas

import { takeEvery, put, call } from 'redux-saga/effects';
import { push } from 'connected-react-router';
import axios from 'axios';

import { Creators, Types } from './reducer';

function* getInterviewFormByIdSaga({ interviewId }) {
  try {
    console.log('abc');
    const url = `http://5d2475ece39785001406ef1f.mockapi.io/test/${interviewId}`;
    const res = yield call(axios.get, url);
    if (res) {
      yield put(Creators.getInterviewFormByIdSuccess(res.data));
    }
  } catch (error) {
    console.error(error);
    yield put(push('/error'));
  }
}
export default [
  takeEvery(Types.GET_INTERVIEW_FORM_BY_ID, getInterviewFormByIdSaga),
];

My container

import React, { useEffect, Fragment } from 'react';
import { Form, Button } from 'antd';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Creators } from '../../redux/interviewForm/reducer';
import Question from './Question';
import FormWrapper from './style';
import Header from './Header';

const InterviewForm = ({
  match,
  interviewForm: { interviewForm },
  form: { getFieldDecorator, validateFields },
  ...props
}) => {
	console.log(props.getInterViewFormById)
  useEffect(() => {
    props.getInterViewFormById(match.params.id);
  }, [props.getInterviewFormById, match]);
  console.log(interviewForm);
  const handleSubmit = e => {
    e.preventDefault();
    validateFields((err, values) => {
      if (!err) {
        console.log('Received values of form: ', values);
      }
    });
  };
  return (
    interviewForm && (
      <Fragment>
        <Header />
        <FormWrapper>
          <Form wrapperCol={{ span: 12 }} onSubmit={handleSubmit}>
            <Fragment>
              {interviewForm.questions.map(question => {
                return (
                  <Question
                    key={question.id}
                    question={question}
                    getFieldDecorator={getFieldDecorator}
                  />
                );
              })}
              <Form.Item>
                <Button type="primary" htmlType="submit">
                  Submit
                </Button>
              </Form.Item>
            </Fragment>
          </Form>
        </FormWrapper>
      </Fragment>
    )
  );
};

InterviewForm.propTypes = {
  getInterviewFormById: PropTypes.func,
  interviewForm: PropTypes.object.isRequired,
  match: PropTypes.object.isRequired,
};

export default connect(
  state => ({
    interviewForm: state.interviewForm,
  }),
  dispatch => ({
    getInterViewFormById: id => dispatch(Creators.getInterViewFormById(id)),
  }),
)(Form.create()(InterviewForm));

Reset all states on logout

Hi,

We're currently working on a RN App started with your awesome Ignite Project !
You should (and you have to) be proud of what your team did and continue to do everyday :-)
Thx a lot ;-)

I'm experiencing a little issue / misunderstanding, and I hope you could bring me help.

So, I'm using redux-sauce, redux-saga and many more (same arch as old ignite project, before beta launched) !

All I want to do is just reset all states on logout action (called from LoginRedux).

You can see my App architecture in the screenshot above :
image

And here's my rootReducer of my Redux index.js :
image

PS : I'm obviously using reduxPersist and it works like a charm.

Again a lot of ๐Ÿ‘ and thx for your help (hoping you can help us) ๐Ÿ˜ƒ

Default values for action creators arguments

I often generate actions that keep consistent properties if not explicitly specified, using default argument values in the action creator method for the associated properties.

Example:

// without reduxsauce
const addTodos = (todos = []) => {
  type: 'ADD_TODOS',
  todos
}
addTodos() // { type: 'ADD_TODOS', todos: [] }

// with reduxsauce
const { Creators } = createActions({
  addTodos: ['todos']
})
Creators.addTodos() // { type: 'ADD_TODOS' }

Please ignore if this is a bad pattern, as this moves the data consistency logic from the reducer to the action creator, but if there is a use for it, the action creator factory could also accept an object describing the arguments more in details:

createActions({
  addTodos: {
    todos: [] // default value
  }
})

createActions - custom example

Hello and thank you for this very good module. I really need an example based on my action because I cannot manage to make it work.

Here is my action:

dispatch({
    type: Types.GET_ALL_POSTS,
    payload: data
});

How this would be transform to createActions? Should be like this?

const { Types, Creators } = createActions({
	getAllPosts: ['data'],
}, {});

Calling an action doesn't update store immediately

I have an action called loadNextCard() which is going to store currentWord in the redux store.
However if for some reason currentWord is null (no more cards), then I want to display an alert and redirect the user.
This is not working because dispatching a redux action doesn't update the store immediately.

  componentWillMount () {
    // Redux action
    this.props.loadNextCard()
    
    if (!this.props.currentWord) {
      Alert.alert(
        'Finished',
        'No more cards, come back later!',
        [
          {text: 'OK', onPress: () => NavigationActions.lessonsList()}
        ]
      )
    }
  }

This is not the only place where I'm having this issue so I would appreciate a solution.
There seems to be a way by passing a callback as suggested here but this doesn't look like a great solution to me. I might as well put it in a setTimeout

How to use namespace and reset action- documentation

The documentation for how to use namespaced actions is not clear.

Say, i utilised the second param in

createActions({
    reset: []
    }, 'MY_NAMESPACE');

Now, if I call reset in my component, should I not see action as MY_NAMESPACE_RESET in redux dev tools? What I see is 'RESET'

Creators.reset();

Here's my handler

export const HANDLERS = {
  [Types.RESET]: reset,
}
// or I tried this as well 
 [Types.MY_NAMESPACE_RESET]: reset

Similarly for the resettable, I'm not sure if I get the hang of the usage completely. A full cycle example is recommended.

Async Actions

Hey!

I'm using this because of Ignite, awesome work on that!

I was wondering how I could handle async requests. I'm definitely missing a piece of the puzzle somewhere.

const { Types, Creators } = createActions({
  search: ['searchTerm'],
  cancelSearch: null,
  fetchRequest: null,
  fetchSuccess: ['data'],
  fetchFailure: ['error'],
})

// ...

export const onSuccess = (state: Object, {data}) => {
 return state.merge ({ results: data });
}

// ...

[Types.FETCH_SUCCESS]: onSuccess,
// ... etc

This top part I think I understand, but where would something like this then go?

export const searchRestos = () => (dispatch: Function, getState: Function) => {
    dispatch(startFetching());

    return api.getRestos()
        .then(response => {
            return dispatch(fetchedData({ response.data }));
        })
        .catch(response => dispatch(errorFetching(response.error)));
};

Thank you again!

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.