Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save barbagrigia/d51a3af1d4806d15e423d706408fed80 to your computer and use it in GitHub Desktop.
Save barbagrigia/d51a3af1d4806d15e423d706408fed80 to your computer and use it in GitHub Desktop.
Reactiflux reduceReducers discussion

[8:19 PM] Rtransat: What is the best way to set data in reducer after we put other data in other reducer?

For example I have a list of items in a reducer:

[{  
amount: 10  
}, {amount: 20}]  

And another reducer with the value null by default and after fetching the first data in the store I want to calculate the sum of the total amount
[8:19 PM] Rtransat: and put it in a store too
[8:19 PM] acemarke:

function* someNumbers() {  
    yield 1;  
    yield 2;  
    yield 3;  
}  
  
const iter = someNumbers();  
console.log(iter.next());  
// {value : 1, more : true}  , or something like that  

[8:19 PM] acemarke: that's basic generator syntax
[8:19 PM] ridencode: i'd call .next() ?
[8:19 PM] ridencode: or saga/redux calls it for me
[8:19 PM] acemarke: the saga middleware calls that for you
[8:19 PM] ridencode: gotit
[8:22 PM] Rtransat: I need to dispatch an action after the end of my fetching data?
[8:23 PM] ridencode: i think so..
[8:24 PM] Rtransat: So I'm fetching my data, put it in state, I use a selector to have the sum of my previous data, and I dispatch an action to put the result in my other state reducer?
[8:25 PM] acemarke: that's one way. You can also restructure your reducer logic so that the "sum things up" reducer runs after the "put everything in place" reducer
[8:26 PM] acemarke: I've got some examples of doing that here: http://blog.isquaredsoftware.com/2017/01/practical-redux-part-7-forms-editing-reducers/#structuring-reducer-logic-for-features and http://redux.js.org/docs/recipes/reducers/BeyondCombineReducers.html
Mark's Dev Blog
Practical Redux, Part 7: Form Change Handling, Data Editing, and...
[8:26 PM] acemarke: roughly:
[8:27 PM] Rtransat: it seems "complicated" :/
[8:28 PM] acemarke:

// before  
const rootReducer = combineReducers({a, b, c, d});  
const store = createStore(rootReducer);  
  
// after  
const mainReducer = combineReducers({a, b, c, d});  
const doMoreThingsReducer = (state, action) => {   
    // do more work here:  
   return newState  
}  
  
const rootReducer = reduceReducers(mainReducer, doMoreThingsReducer);  
const store = createStore(rootReducer);  

[8:28 PM] ridencode: what is reduce reducers
[8:29 PM] acemarke: https://github.com/acdlite/reduce-reducers
GitHub
acdlite/reduce-reducers
reduce-reducers - Reduce multiple reducers into a single reducer.
[8:29 PM] ridencode: sure'd be nice if we knew all tehse functions you know of haha
[8:29 PM] Rtransat: I did not understand your example x)
[8:29 PM] acemarke: which is, in its entirety:

export default function reduceReducers(...reducers) {  
  return (previous, current) =>  
    reducers.reduce(  
      (p, r) => r(p, current),  
      previous  
    );  
}  

[8:29 PM] acemarke: basically, call each reducer in turn, pass the result to the next one
[8:30 PM] Rtransat: @acemarke so I have 2 reducer, I do my logic sum and after I merge the reducer into one?
[8:30 PM] ridencode: ohh
[8:30 PM] ridencode: makes perfect sense now
[8:30 PM] acemarke:

const timesTwo = (state, action) => state * 2;  
const addFour = (state, action) => state + 4;  
  
const rootReducer = reduceReducers(timesTwo, addFour);  
rootReducer(5, dontCare); // 14  

[8:30 PM] acemarke: or at least, it should be that
[8:30 PM] ridencode: your before example just didn't show enough code so it was confusing
[8:31 PM] acemarke: again, making things up as I go :)
[8:31 PM] acemarke: so, rootReducer is one big function
[8:31 PM] Rtransat: but if I need to recalculate thing when the amount change?
[8:32 PM] Rtransat: I have DepartmentList with amount and other one called with total amount from previous department. When previous department change the total need to be recalculate everytime
[8:33 PM] acemarke: sure. Your "second reducer" could do that
[8:33 PM] Rtransat: Why I need to have the total in the same reducer?
[8:33 PM] Rtransat: Or, why I need to merge the total in the same reducer
[8:33 PM] acemarke: Redux only has one reducer function in the store
[8:34 PM] acemarke: but that one "root reducer" can be made up of many functions inside
[8:34 PM] ridencode: lol
[8:34 PM] Rtransat: @acemarke Yes, I wanted to say functions from reducer not reducer itself
[8:35 PM] acemarke: hmm. I'm not quite sure I understand the question
[8:35 PM] acemarke: lemme see if I can explain it another way
[8:36 PM] acemarke: the typical approach is to just use combineReducers to produce the root reducer
[8:36 PM] acemarke: which splits up the work based on the keys in your state
[8:36 PM] acemarke:

const rootReducer = combineReducers({  
    users : userReducer,  
    items : itemsReducer  
});  

[8:36 PM] acemarke: neither of those slice reducers cares about the "timing" at all
[8:37 PM] acemarke: but now, you're wanting to introduce some ordering into the work
[8:37 PM] acemarke: you need Part A to be done first, so that another chunk of logic can read the values
[8:37 PM] Rtransat: @acemarke this state if fine?

{  
  departments: [  
    {name: 'foo', amount: 100},  
    {name: 'bar', amount: 200},  
  ],  
  totalAmount: 300   
}  

[8:37 PM] acemarke: sure
[8:37 PM] ridencode: lol
[8:38 PM] acemarke: it's just a question of how you want to organize the logic to update that state
[8:38 PM] acemarke: here's one other thought
[8:38 PM] ridencode: just child nest all the actions?
[8:38 PM] acemarke: you don't have to keep totalAmount in state
[8:38 PM] Rtransat: I fetch departments, I calculate total and I dispatch an action to calculate the sum? Nothing wrong about that?
[8:38 PM] acemarke: you can derive it
[8:38 PM] ridencode: for order of reducers
[8:38 PM] acemarke: yeah, you can also dispatch multiple actions if you want to
[8:38 PM] acemarke: so for the "deriving" aspect
[8:38 PM] acemarke: if I have [100, 200] in state already
[8:38 PM] acemarke: I don't have to keep the calculated total in state too
[8:39 PM] acemarke: I can just read those values and add them up when I need to use the result
[8:39 PM] Rtransat: So the total will be in a local state component?(edited)
[8:41 PM] Rtransat: @acemarke so you know my problem better than before πŸ˜ƒ I don't need reduce reducer thing?
[8:41 PM] sheldonj: Don't put computed values in your reducers. Use memoized selectors.
[8:42 PM] acemarke: it's basically the same answer as this FAQ question: http://redux.js.org/docs/faq/Reducers.html#reducers-share-state
[8:42 PM] acemarke: you can add more reducer logic in sequence; you can dispatch multiple actions; you can derive the results as needed
[8:42 PM] Rtransat: I never memoized thing yet ^^. My app is not huge πŸ˜‰
[8:42 PM] acemarke: all valid approaches
[8:42 PM] sheldonj: https://github.com/reactjs/reselect
GitHub
reactjs/reselect
reselect - Selector library for Redux
[8:43 PM] Rtransat: Ok, I need to choose one of them then
[8:43 PM] sheldonj: If you need a simple solution
[8:43 PM] sheldonj: Then just create a simple function in your mapStateToProps
[8:43 PM] Rtransat: @sheldonj memoize is good for perf? I don't need perf yet πŸ˜ƒ I'm still practicing with little project
[8:44 PM] sheldonj: You don't need to memoize to leverage selectors
[8:44 PM] Rtransat: I use selector (but without memoization ;))
[8:45 PM] Rtransat: @acemarke I think I will use "derivate" approach πŸ˜‰
[8:45 PM] Rtransat: Thx guys πŸ˜‰
[8:45 PM] acemarke: sure
[8:48 PM] Rtransat: @acemarke If you have some time left for me πŸ˜„ https://www.webpackbin.com/bins/-KfYlOkauSTjrAWCDUnz

The comment in my component seems fair to you?
[8:49 PM] acemarke: sure. I'd suggest adding curly braces and doing the calculation before you return the JSX, but should be fine
[8:50 PM] acemarke:

const DepartmentList = ({  
  departments  
}) => {  
    const total = departments.reduce( (sum, dept) => {  
         return sum + dept.amount;  
    }, 0);  
  
    return (  
      <div>  
        <h1>Liste des dΓ©partements</h1>  
        {departments.map((department) => <Department {...department}/>)}  
        <DeptTotal total={total} />  
      </div>  
    )  
}  

[8:50 PM] Rtransat: I need the same structure as my department because when the total change I will need to display the sum of everything x)
[8:50 PM] everdimension: @acemarke this reduceReducers stuff is very interesting, thanks for bringing it up

I saw it some time ago and could not get why it would be used but now I see it much more clearly.
[8:50 PM] acemarke: sure
[8:51 PM] acemarke: one way to think of it
[8:51 PM] acemarke: the original "Flux architecture" included this store.waitFor() idea
[8:51 PM] acemarke: ie, you need some ordering when stores update
[8:51 PM] acemarke: with Redux, there's only one store
[8:51 PM] acemarke: now the "stores" are functions
[8:51 PM] acemarke: so, if you want to have ordering between functions.... write logic that calls functionA first, and functionB second :)
[8:52 PM] Rtransat: @acemarke Thx dude ^^
[8:53 PM] Rtransat: I'm still trying to understand the reduce reducer thing but even when your explaination, don't get it at all :/
[8:54 PM] acemarke: go back to this example:

const timesTwo = (state, action) => state * 2;  
const addFour = (state, action) => state + 4;  
  
const rootReducer = reduceReducers(timesTwo, addFour);  
rootReducer(5, dontCare); // 14  

[8:54 PM] acemarke: when we call rootReducer(), it first calls timesTwo(5, undefined)
[8:54 PM] acemarke: that returns 10
[8:54 PM] acemarke: then it takes that result, and calls addFour(10, undefined)
[8:54 PM] acemarke: and it takes the result of addFour, and that's the final result
[8:55 PM] acemarke: it's a pipeline
[8:56 PM] acemarke: so, if we have:

const mainReducer = combineReducers({a, b, c, d});  
const doMoreThingsReducer = (state, action) => {   
    // do more work here:  
   return newState  
}  
  
const rootReducer = reduceReducers(mainReducer, doMoreThingsReducer);  
const store = createStore(rootReducer);  

[8:56 PM] acemarke: when the action is dispatched, it first goes to mainReducer(state, action)
[8:56 PM] Rtransat: I understand until the dontCare variable
[8:56 PM] Rtransat: if dontCare is 10 or 1000 or 5000 the result will be the same?
[8:56 PM] acemarke: that's just me saying we're calling rootReducer ourselves, and our example functions don't care about actions
[8:56 PM] acemarke: replace that with rootReducer(5, undefined)
[8:57 PM] acemarke: you can see that timesTwo and addFour don't use the action parameter
[8:57 PM] acemarke: here, lemme try another variation
[8:58 PM] acemarke:

const times= (state, action) => state * action.times;  
const add= (state, action) => state + action.add;  
  
const rootReducer = reduceReducers(times, add);  
rootReducer(5, {times : 2, add : 4}); // 14  

[8:59 PM] Rtransat: Ok I get it (finally x))
[8:59 PM] acemarke: yay!
[8:59 PM] acemarke: so back to the last code sample I pasted before this
[8:59 PM] acemarke: mainReducer will run first
[8:59 PM] acemarke: and do everything like normal
[8:59 PM] Rtransat: It was your value example, I was lost with : rootReducer(5, dontCare); // 14 [9:00 PM] Rtransat: the result 14
[9:00 PM] acemarke: but instead of that being the only work done, now we pass the mainReducer result to doMoreThingsReducer
[9:00 PM] Rtransat:

const timesTwo = (state, action) => state * 2;  
const addFive = (state, action) => state + 5;  
  
const rootReducer = reduceReducers(timesTwo, addFive);  
rootReducer(5, dontCare); // 15  

15 is the result?
[9:01 PM] acemarke: yes
[9:02 PM] Rtransat: so the addFive take the value from the previous reducer function, right? πŸ˜ƒ
[9:02 PM] Rtransat: Please tell me I understood x)
[9:02 PM] acemarke: yep
[9:02 PM] Rtransat: But in which case we need something like that?
[9:02 PM] acemarke: like I said earlier: when you have some logic that needs part of the work done first
[9:03 PM] acemarke: instead of the usual "I don't care about timing, just (state, action) => newState "
[9:03 PM] acemarke: if I have const mainReducer = combineReducers({a, b, c, d})
[9:03 PM] acemarke: the bReducer doesn't care whether it's called first, last, or in the middle
[9:04 PM] acemarke: but in your example, you needed the list of amounts to be updated first, then read the amounts to calculate the total
[9:05 PM] Rtransat: @acemarke yes πŸ˜‰
[9:06 PM] acemarke: here's one other possible way to write this
[9:07 PM] Rtransat: So with reduce reducer I will have my amounts value from the reducerA, now I know I have my value for calculate total, then I calculate total from previous reducer result(edited)
[9:07 PM] Rtransat: ?
[9:08 PM] acemarke:

function rootReducer(state, action) {  
    let newState = mainReducer(state, action);  
  
    if(action.type === "UPDATE_DEPARTMENTS") {  
        newState.departments.total = calculateTotal(newState.departments.departments);  
    }  
  
    return newState;  
}  

[9:08 PM] acemarke: and yes
[9:09 PM] Rtransat: I get it. Thank you @acemarke πŸ˜ƒ
[9:09 PM] acemarke: sure
[9:10 PM] Rtransat: You're so much help 😎
[9:10 PM] acemarke: :)
[9:10 PM] Rtransat: I understood but at the end I will use the "derivate" thing x)
[9:11 PM] Rtransat: more simple for my case (I think)
[9:11 PM] acemarke: sure. and much of the time, that's the best approach
[9:11 PM] Rtransat: Yes πŸ˜‰
[9:12 PM] Rtransat: reduce reducer is rather unusual in general?
[9:14 PM] acemarke: it's definitely not as well known as combineReducers, because combineReducers comes with Redux, and that's enough for most people
[9:15 PM] Rtransat: And like you told me, you can use the "derivate" approach rather than put the total value in the state

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment