Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

History updated with filtered action #106

Closed
xiceph opened this issue Jul 29, 2016 · 42 comments

Comments

@xiceph
Copy link

commented Jul 29, 2016

I found that filtered action after unfiltered one change history too.
Here is debug output:
action SET_FEATURES is only allowed (not filtered)

%credux-undo font-style: italic action SET_FEATURES 
prev history Object { past: Array[1], present: Object, future: Array[0] } 
action Object { type: "SET_FEATURES", features: Array[2] } 
next history Object { past: Array[2], present: Object, future: Array[0] } 
inserting Object { list: Array[2], selected: Array[0], hovered: null } 
 new free:  8 
 inserted new state into history 

%credux-undo font-style: italic action SET_HOVERED 
prev history Object { past: Array[2], present: Object, future: Array[0] } 
action Object { type: "SET_HOVERED", feature: Object } 
next history Object { past: Array[3], present: Object, future: Array[0], wasFiltered: true } 
inserting Object { list: Array[2], selected: Array[0], hovered: Object } 
 new free:  7 
 filter prevented action, not storing it 

%credux-undo font-style: italic action SET_HOVERED 
prev history Object { past: Array[3], present: Object, future: Array[0], wasFiltered: true } 
action Object { type: "SET_HOVERED", feature: Object } 
next history Object { past: Array[3], present: Object, future: Array[0], wasFiltered: true } 
inserting Object { list: Array[2], selected: Array[0], hovered: Object } 
 new free:  6 
 filter prevented action, not storing it 

%credux-undo font-style: italic action SET_HOVERED 
prev history Object { past: Array[3], present: Object, future: Array[0], wasFiltered: true } 
action Object { type: "SET_HOVERED", feature: null } 
next history Object { past: Array[3], present: Object, future: Array[0], wasFiltered: true } 
inserting Object { list: Array[2], selected: Array[0], hovered: Object } 
 new free:  6 
 filter prevented action, not storing it 

As you can see in first SET_HOVERED action changed the history (past array increased to 3 items), however output says: filter prevented action, not storing it.
Just updated to beta9, issue is still there.

@xiceph

This comment has been minimized.

Copy link
Author

commented Jul 29, 2016

It looks like connected to #86 and followed.
The past is updated when it should but one more time on next action. In other words, 2 non-fitered actions cause 3 changes in history, which for me looks like a bug.
The result is that present and last item in past array are the same and then undo action (the first one) do nothing obviously.

@omnidan

This comment has been minimized.

Copy link
Owner

commented Jul 29, 2016

@xiceph could you provide a small example repo to make it easier to reproduce and fix this issue? something similar to this: https://github.com/peteruithoven/redux-undo-filtering-issue-example would be perfect 😁

@omnidan omnidan added the bug label Jul 29, 2016

@xiceph

This comment has been minimized.

Copy link
Author

commented Jul 30, 2016

I'll try to separate a basic example. I'll not be easy, it is part of a complex scenario, but maybe just this clear a root of the issue.
Unfortunately It came in bad time, I'll not have time for this in maybe next ten days.

@xiceph

This comment has been minimized.

Copy link
Author

commented Aug 11, 2016

I found another way to reproduce the issue, so I easily prepared a small example repo:
https://github.com/xiceph/redux-undo-problem-example

@AsafShochet

This comment has been minimized.

Copy link

commented Aug 29, 2016

hi, is there any solution to this bug?
It happens for us also..

@omnidan

This comment has been minimized.

Copy link
Owner

commented Sep 6, 2016

@AsafShochet unfortunately, not yet... there are a few issues to the current filtering system and no decent solution that fixes all of the problems yet.

@nickjanssen

This comment has been minimized.

Copy link

commented Sep 12, 2016

If it helps, here's a very small test demonstrating the problem:

import test from 'ava'
import expect from 'expect'
import undoable from 'redux-undo'

const reducer = (state = 0, action) => {
  if (action.type === 'INCREMENT') return state + 1
  return state
}

const undoableReducer = undoable(reducer, {
  filter: () => false
})

test(`state past should not exist`, () => {
  let result = undoableReducer(undefined, { type: 'INCREMENT' })
  expect(result.present).toEqual(1)
  expect(result.past.length).toEqual(0)
})
@livemixlove

This comment has been minimized.

Copy link

commented Oct 30, 2016

Eta on this? My current partial workaround is to keep track of the actions in a stack in the state, and clearHistory() after any filtered action when past.length = 1.

@joyfulelement

This comment has been minimized.

Copy link

commented Nov 3, 2016

Encountered the same issue too. The filtered action still getting pushed to the past array.

@livemixlove

This comment has been minimized.

Copy link

commented Nov 4, 2016

I feel like filters should be labeled as an unfinished/broken feature due to this bug. It breaks the fundamental expected functionality.

@joyfulelement

This comment has been minimized.

Copy link

commented Nov 4, 2016

I've experimented a bit with version 1.0-beta9, and even with the custom filters definition below, the past array still contain state changes triggered by ACTION_TO_SKIP_FROM_HISTORY. Had been quite confused with the current behaviour I'm seeing.

const reducers = undoable(reducersPureFunc, {undoType: CUSTOM_ACTION, filter: function filterActions(action, presentState, pastState) {
    return action.type !== ACTION_TO_SKIP_FROM_HISTORY; 
  }
})
@joyfulelement

This comment has been minimized.

Copy link

commented Nov 4, 2016

After going back and forth with different package versions, I am certain that this issue was introduced with version 1.0.0-beta8 (perhaps with #86), therefore, if you are stuck with this particular issue, reverting back to redux-undo: 1.0.0-beta7 would help as a temporary workaround. If yarn is used as the dependency management tool, the command yarn add redux-undo@1.0.0-beta7 will get you this version installed.

@csgruenebe

This comment has been minimized.

Copy link

commented Nov 21, 2016

@joyfulelement I can confirm the exact same problem with beta9. Will wait for fix and use 1.0.0-beta7 until then :)

@livemixlove

This comment has been minimized.

Copy link

commented Nov 24, 2016

Without doing a test, I'm pretty sure that 1.0.0-beta7 still has the issue exemplified in https://github.com/xiceph/redux-undo-problem-example but seems to work for my most common use case of having filtered out data-loading actions before non-filtered-out editing actions.

@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Dec 14, 2016

I've fixed this in the following pull request: #139

The basic idea is to save an extra copy of the most recently unfiltered state until a new unfiltered action takes place, or someone clicks undo/redo/jump

omnidan added a commit that referenced this issue Dec 20, 2016

Merge pull request #139 from davidroeca/master
Unfiltered snapshots feature, closes #98 #95 #106 #103
@omnidan

This comment has been minimized.

Copy link
Owner

commented Dec 20, 2016

I published a new version beta9-3 that should fix this issue (thanks a lot, @davidroeca 🎉)

Let me know if it works now and feel free to reopen if you're still having issues! 😁

@omnidan omnidan closed this Dec 20, 2016

@jide

This comment has been minimized.

Copy link

commented Jan 18, 2017

I have the same issue, filtered actions being fired although they are excluded in filter.

I'm using beta9-5.

@omnidan

This comment has been minimized.

Copy link
Owner

commented Jan 19, 2017

@jide can you check if it works with beta9-3? If not, please let us know how to reproduce your issue. /cc @davidroeca

@jide

This comment has been minimized.

Copy link

commented Jan 19, 2017

beta9-3, beta9-5: Same issue

beta7: Works

@jide

This comment has been minimized.

Copy link

commented Jan 19, 2017

I'll try to make a reproducible test, I have to find out what's causing this.

@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 19, 2017

I've run the following test on beta9-5 and beta9-3, and it works:

import assert from 'assert'
import { describe, it } from 'mocha'
import undoable from 'redux-undo'

const reducer = (state = 0, action) => {
  if (action.type === 'INCREMENT') return state + 1
  return state
}

const undoableReducer = undoable(reducer, {
  filter: () => false
})

describe('check undo stuff', () => {
  let result = undoableReducer(undefined, { type: 'INCREMENT' })
  it('should ensure state past does not exist', () => {
    assert.equal(result.present, 1)
    assert.equal(result.past.length, 0)
  })
})

An example would be very helpful

@davidroeca davidroeca reopened this Jan 19, 2017

@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 26, 2017

@jide any word on this? If this isn't an issue anymore, I'll go ahead and close it

@jide

This comment has been minimized.

Copy link

commented Jan 27, 2017

hi @davidroeca, I made a failing test, shouldn't both tests should return the same ?

import expect from 'expect'
import undoable, { excludeAction, ActionTypes } from 'redux-undo'

function reducer(state = {}, action) {
  switch (action.type) {
    case 'UNFILTERED_ACTION':
      return {
        ...state,
        foo: action.payload
      }
    case 'FILTERED_ACTION':
      return {
        ...state,
        bar: action.payload
      }
    default:
      return state
  }
}

describe('check filtered actions', () => {
  it('passes', () => {
    const undoableReducer = undoable(reducer, {
      filter: excludeAction(['FILTERED_ACTION'])
    })
    let result = undoableReducer(undefined, { type: 'FILTERED_ACTION', payload: 2 })
    result = undoableReducer(result, { type: 'UNFILTERED_ACTION', payload: 1 })
    result = undoableReducer(result, { type: ActionTypes.UNDO })
    result = undoableReducer(result, { type: ActionTypes.UNDO })

    expect(result.present).toEqual({ bar: 2 })
    expect(result.past.length).toEqual(0)
  })

  it('fails', () => {
    const undoableReducer = undoable(reducer, {
      filter: excludeAction(['FILTERED_ACTION'])
    })
    let result = undoableReducer(undefined, { type: 'UNFILTERED_ACTION', payload: 1 })
    result = undoableReducer(result, { type: 'FILTERED_ACTION', payload: 2 })
    result = undoableReducer(result, { type: ActionTypes.UNDO })
    result = undoableReducer(result, { type: ActionTypes.UNDO })

    expect(result.present).toEqual({ bar: 2 })
    expect(result.past.length).toEqual(0)
  })
})
@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 27, 2017

edited--I wrote a previous post, but I have a different fix.

@jide the fix is to provide an up-front initial state. I'll look into why this is, but it looks like passing undefined and relying on the default arguments of the reducer causes strange behavior. You should be able to undo all the way to the initial state if you provide an empty object {} to the reducer as the starting state:

import expect from 'expect'
import undoable, { excludeAction, ActionTypes } from 'redux-undo'

function reducer(state = {}, action) {
  switch (action.type) {
    case 'UNFILTERED_ACTION':
      return {
        ...state,
        foo: action.payload
      }
    case 'FILTERED_ACTION':
      return {
        ...state,
        bar: action.payload
      }
    default:
      return state
  }
}

describe('check filtered actions', () => {
  it('passes', () => {
    const undoableReducer = undoable(reducer, {
      filter: excludeAction(['FILTERED_ACTION'])
    })
    let result = undoableReducer({}, { type: 'FILTERED_ACTION', payload: 2 })
    result = undoableReducer(result, { type: 'UNFILTERED_ACTION', payload: 1 })
    result = undoableReducer(result, { type: ActionTypes.UNDO })
    //result = undoableReducer(result, { type: ActionTypes.UNDO })
    
    expect(result.present).toEqual({ })
    expect(result.past.length).toEqual(0)
  })

  it('fails', () => {
    const undoableReducer = undoable(reducer, {
      filter: excludeAction(['FILTERED_ACTION'])
    })
    let result = undoableReducer({}, { type: 'UNFILTERED_ACTION', payload: 1 })
    result = undoableReducer(result, { type: 'FILTERED_ACTION', payload: 2 })
    result = undoableReducer(result, { type: ActionTypes.UNDO })
    result = undoableReducer(result, { type: ActionTypes.UNDO })

    expect(result.present).toEqual({ })
    expect(result.past.length).toEqual(0)
  })
})

There are only two things to undo--the change from initial state, and the change caused by the unfiltered action. In fact, you only have to undo once in the first scenario since the state only registers the change caused by the unfiltered action and it's the present state in that case.

As for why undefined doesn't work, that might require a deeper look

@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 27, 2017

Found the bug: https://github.com/omnidan/redux-undo/blob/master/src/reducer.js#L172

the other assignments follow the pattern

history = config.history = {{initializedHistory}

while this one only has

history = {{initializedHistory}}

This forces the history to get initialized twice

omnidan added a commit that referenced this issue Jan 27, 2017

Merge pull request #144 from omnidan/fix_undefined_initialization
assign config.history to the initialized history when passed initial state is undefined

This fixes inconsistent undo behavior when initial state is undefined, as mentioned in #106
@omnidan

This comment has been minimized.

Copy link
Owner

commented Jan 27, 2017

@jide @davidroeca I published beta9-6 with this fix, please let me know if everything works fine now 😁

@jide

This comment has been minimized.

Copy link

commented Jan 28, 2017

Hey !

Hmmm... There's something I do not understand in your updated test: Why would the final state, after the undo(s), be {} ? If an action is filtered out, its effect should not be undone, so the state should be { bar: 2 } right ? (in both tests)

What confuses me even more is that your first test fails, since the final state actually is { bar: 2 } (confess, you did not run that test 😁)

In my test, beta9-6 gives the same effect as using {} as the first argument of undoableReducer, result.present is {}. Which I guess was the goal of the fix.

I also tried beta9-6 in a project (where beta-7 works fine), and it threw :

app.js:104488 Uncaught TypeError: Cannot read property 'length' of undefined
    at lengthWithoutFuture (app.js:104488)

Referring to :

  // lengthWithoutFuture: get length of history
  function lengthWithoutFuture(history) {
    return history.past.length + 1;
  }

🤔

@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 28, 2017

Yes, that was the goal of my test. The filtering behavior changed from beta-7, so that's probably why you're experiencing an issue where you expect something different.

Not sure what you're talking about about the test failing. I pasted that test into the source code of the master branch with the only change being an import switch of 'redux-undo' -> '../src' and every test passed with the addition of my two.

Yes, beta 9-6 should provide you the same behavior passing undefined and {} to the undoable reducer because... well... {} is your default argument state in the reducer.

The thought behind the filtering behavior is that an unfiltered actions serve as a marker for where you undo back to. So even if a series of actions is filtered (e.g. say you want to filter a 10 actions that fire as a result of one user action), you can undo to the last unfiltered action.

For test 1, you should only be able to undo and redo between {foo: 1, bar: 2} and {}, while for test 2, you should only be able to undo and redo between { foo: 1 } and {} since the filtered action's state is lost upon undo. So actually in both tests, you only have to undo once to get to {}.

Hope this helps with any confusion--let me know if you have any other questions regarding the new filtering behavior.

@jide

This comment has been minimized.

Copy link

commented Jan 28, 2017

Sorry about the failing test, I realized I ran it using beta7...

But, to sum things up, using your test :

  • beta 7 :
    • test 1 fails. Final state is { bar: 2 }
    • test 2 passes. Final state is {}
  • beta 9-5 and beta9-6 :
    • test 1 passes. Final state is {}
    • test 2 passes. Final state is {}

But actually, the only result that makes sense to me is the first failing test with beta 7. Here is the output if I log after each action :

LOG: [], Object{bar: 2}
LOG: [Object{bar: 2}], Object{bar: 2, foo: 1}
LOG: [], Object{bar: 2}

While beta 9-x gives :

LOG: [], Object{bar: 2}
LOG: [Object{}], Object{bar: 2, foo: 1}
LOG: [], Object{}

Here is another test I tried, with an initial state before calling the first action so we can undo it :

describe('check filtered actions', () => {
  it('passes', () => {
    const undoableReducer = undoable(reducer, {
      filter: excludeAction(['FILTERED_ACTION'])
    })
    let result = undoableReducer({})
    console.log(result.past, result.present);
    result = undoableReducer(result, { type: 'FILTERED_ACTION', payload: 2 })
    console.log(result.past, result.present);
    result = undoableReducer(result, { type: 'UNFILTERED_ACTION', payload: 1 })
    console.log(result.past, result.present);
    result = undoableReducer(result, { type: ActionTypes.UNDO })
    console.log(result.past, result.present);
    result = undoableReducer(result, { type: ActionTypes.UNDO })
    console.log(result.past, result.present);

    expect(result.present).toEqual({ })
    expect(result.past.length).toEqual(0)
  })
})

Output in beta7 :

LOG: [], Object{}
LOG: [], Object{bar: 2}
LOG: [Object{bar: 2}], Object{bar: 2, foo: 1}
LOG: [], Object{bar: 2}
LOG: [], Object{bar: 2}

Output in beta9-x :

LOG: [], Object{}
LOG: [], Object{bar: 2}
LOG: [Object{}], Object{bar: 2, foo: 1}
LOG: [], Object{}
LOG: [], Object{}

Maybe there is something I do not understand, but to me, philosophically, triggering 2 consecutive actions that modify the state, and undoing both actions, one filtered and one unfiltered, should result in the state being as if we only triggered the unfiltered action.

@jide

This comment has been minimized.

Copy link

commented Jan 28, 2017

Btw, this throws the error I get when using 9-6 in my project :

describe('check filtered actions', () => {
  it('passes', () => {
    const undoableReducer = undoable(reducer, {
      filter: excludeAction(['FILTERED_ACTION'])
    })
    let result = undoableReducer({})
    result = undoableReducer({})
    result = undoableReducer(result, { type: 'FILTERED_ACTION', payload: 2 })
    result = undoableReducer(result, { type: 'UNFILTERED_ACTION', payload: 1 })

    expect(result.present).toEqual({ })
    expect(result.past.length).toEqual(0)
  })
})
undefined is not an object (evaluating 'history.past.length')
@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 30, 2017

@jide try not to pass subsequent undefined actions to undoable reducer, or you'll experience unexpected behavior. In fact, I wouldn't recommend passing undefined actions to the undoable reducer, period. Always use two arguments.

let result = undoableReducer({}) // bad
...
let result = undoableReducer({}, { type: 'UNFILTERED_ACTION', payload: 2 }) // better

When fixed, the above test will fail since the present is { bar: 2, foo: 1 }.

In response to:

Maybe there is something I do not understand, but to me, philosophically, triggering 2 consecutive actions that modify the state, and undoing both actions, one filtered and one unfiltered, should result in the state being as if we only triggered the unfiltered action.

You cannot undo a filtered action. This is by design. When a filtered action occurs, the present state gets updated, but not the past. Hence, if you undo after a filtered action, you'll undo as though your state were that of the latest unfiltered action, and will thus undo to the initial state ({}) in both orderings of filtered -> unfiltered or unfiltered -> filtered. When you undo a filtered action, you can only redo to the latest unfiltered state, so in the filtered -> unfiltered case, you'll be redoing to { bar: 2, foo: 1} and in the unfiltered -> filtered case you'll be redoing only to { foo: 1 }.

The following tests should pass:

import expect from 'expect'
import undoable, { excludeAction, ActionTypes } from 'redux-undo'

function reducer(state = {}, action) {
  switch (action.type) {
    case 'UNFILTERED_ACTION':
      return {
        ...state,
        foo: action.payload
      }
    case 'FILTERED_ACTION':
      return {
        ...state,
        bar: action.payload
      }
    default:
      return state
  }
}

describe('check filtered actions', () => {
  it('passes', () => {
    const undoableReducer = undoable(reducer, {
      filter: excludeAction(['FILTERED_ACTION'])
    })
    let result = undoableReducer({}, { type: 'FILTERED_ACTION', payload: 2 })
    result = undoableReducer(result, { type: 'UNFILTERED_ACTION', payload: 1 })
    result = undoableReducer(result, { type: ActionTypes.UNDO })

    expect(result.present).toEqual({ })
    expect(result.past.length).toEqual(0)
    expect(result.future.length).toEqual(1)

    result = undoableReducer(result, { type: ActionTypes.REDO })

    expect(result.present).toEqual({ bar: 2, foo: 1 })
    expect(result.past.length).toEqual(1)
    expect(result.future.length).toEqual(0)
  })

  it('passes', () => {
    const undoableReducer = undoable(reducer, {
      filter: excludeAction(['FILTERED_ACTION'])
    })
    let result = undoableReducer({}, { type: 'UNFILTERED_ACTION', payload: 1 })
    result = undoableReducer(result, { type: 'FILTERED_ACTION', payload: 2 })
    result = undoableReducer(result, { type: ActionTypes.UNDO })

    expect(result.present).toEqual({ })
    expect(result.past.length).toEqual(0)
    expect(result.future.length).toEqual(1)

    result = undoableReducer(result, { type: ActionTypes.REDO})

    expect(result.present).toEqual({ foo: 1 })
    expect(result.past.length).toEqual(1)
    expect(result.future.length).toEqual(0)
  })
})
@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 30, 2017

closing for now

@davidroeca davidroeca closed this Jan 30, 2017

@jide

This comment has been minimized.

Copy link

commented Jan 31, 2017

@davidroeca : Ok, I get the way it works, I'm surprised to be alone to have my use case. I will create a separate reducer with these actions I guess.

Otherwise, I found a fix for the undefined is not an object (evaluating 'history.past.length') error by doing some debugging in my app. Not sure why though, but thought I should put it here :

const undoableReducer = undoable(reducer, {
  // ...
  initTypes: ['@@INIT']
});
@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 31, 2017

What are you expecting the filter to do for you if it isn't to prevent the past from being updated, and to subsequently prevent you from undoing/redoing a filtered action's state changes?

@jide

This comment has been minimized.

Copy link

commented Jan 31, 2017

Undoing when having an unfiltered action and then a filtered action would first undo the unfiltered action and then replay the filtered mutation on the result, keeping its effects.

@jide

This comment has been minimized.

Copy link

commented Jan 31, 2017

const initialState = {}
// {}
let result = undoableReducer(initialState, { type: 'UNFILTERED_ACTION', payload: 1 })
// { unfiltered: 1 }
result = undoableReducer(result, { type: 'FILTERED_ACTION', payload: 'A' })
// { unfiltered: 1, filtered: 'A' }
result = undoableReducer(result, { type: 'UNFILTERED_ACTION', payload: 2 })
// { unfiltered: 2, filtered: 'A' }
result = undoableReducer(result, { type: ActionTypes.UNDO })
// { unfiltered: 1, filtered: 'A' }
result = undoableReducer(result, { type: ActionTypes.UNDO })
// Would do :
// 1: go back to the state before the first UNFILTERED_ACTION
// {}
// 2: Reapply state mutation introduced by FILTERED_ACTION afterwards :
// { filtered: 'A' }

So in the end this would be equivalent to not having triggered UNFILTERED_ACTION at all. Which I find logical in the sense of "I have undone UNFILTERED_ACTION, and leave out FILTERED_ACTION from the undo stuff".

@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 31, 2017

So you're expecting that filtered action state changes can never be undone in the context of the reducer. This is essentially the same thing as storing the data in a separate reducer that isn't undoable, so I'd recommend just doing that.

@jide

This comment has been minimized.

Copy link

commented Jan 31, 2017

Yes unfortunately it is not always possible to use a separate reducer I think (e.g. when you access this part of the state somewhere in the reducer). In my own case it should be ok though.

@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 31, 2017

At that point, you could also use https://github.com/gaearon/redux-thunk to use logic for the entire state. But I see what you mean, especially if you use another library.

This library could benefit from a third categorization of actions, but would also add a decent amount of complexity of this library's code so I don't know if it's worth it to add this feature at this point. Definitely something worth thinking about though. @omnidan thoughts?

@livemixlove

This comment has been minimized.

Copy link

commented Jan 31, 2017

@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 31, 2017

You can also compose your reducers at a lower level. Use combineReducers when composing subsections of a reducer that are undoable and not undoable. Just pay attention to the fact that the undoable portion must reference the present.

import expect from 'expect'
import { combineReducers } from 'redux'
import undoable, { excludeAction, ActionTypes } from 'redux-undo'

const subReducer1 = (state = 0, action) => {
  switch (action.type) {
    case 'UNDOABLE_ACTION':
      return action.payload
    default:
      return state
  }
}

const subReducer1WithUndo = undoable(subReducer1)

const subReducer2 = (state = 0, action) => {
  switch (action.type) {
    case 'STATIC_ACTION':
      return action.payload
    default:
      return state
  }
}

const reducer = combineReducers({
  undoable: subReducer1WithUndo,
  static: subReducer2
})

describe('check filtered actions', () => {
  it('passes', () => {
    let result = reducer(undefined, { type: 'STATIC_ACTION', payload: 2 })
    result = reducer(result, { type: 'UNDOABLE_ACTION', payload: 1 })
    result = reducer(result, { type: ActionTypes.UNDO })

    expect(result.undoable.present).toEqual(0)
    expect(result.static).toEqual(2)

    result = reducer(result, { type: ActionTypes.REDO })

    expect(result.static).toEqual(2)
    expect(result.undoable.present).toEqual(1)

  })

  it('passes', () => {
    let result = reducer({}, { type: 'UNDOABLE_ACTION', payload: 1 })
    result = reducer(result, { type: 'STATIC_ACTION', payload: 2 })
    result = reducer(result, { type: ActionTypes.UNDO })

    expect(result.undoable.present).toEqual(0)
    expect(result.static).toEqual(2)

    result = reducer(result, { type: ActionTypes.REDO})

    expect(result.undoable.present).toEqual(1)
    expect(result.static).toEqual(2)
  })
})
@davidroeca

This comment has been minimized.

Copy link
Collaborator

commented Jan 31, 2017

Likewise, if you need any more specific logic, you can rewrite combineReducers yourself. Check http://redux.js.org/docs/basics/Reducers.html#splitting-reducers and search for the place combineReducers is referenced

const reducer = (state, action) => {
  // Add additional logic that handles additional complexity.
  // Potentially return something different than what you would get from combineReducers
  return {
    undoable: subReducer1WithUndo(state.undoable, action),
    static: subReducer2(state.static, action)
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants
You can’t perform that action at this time.