How to use reselection in the change list when the objects are the same?

I use reselect to get parts of my redux state. I have a list of objects in my state. One of my subselectors for my create selector is the filter function of this list:

state => state.list.filter(filterFunction) 

So, I pass this to my createSelector:

 createSelector( state => state.list.filter(filterFunction), (subsetOfObjects) => subsetOfObjects.map(doSomething) ); 

This filter function returns a subset of my objects in the list. Therefore, if the list changes, reselecting always returns a new object, even if the subset has not changed because the list does not match (completely correct).

Is it possible to get only a new object, if there are changes in the objects or the filtered list?

+6
source share
2 answers

In the end, I had an idea that could work:

 const list = { object1: { id: 'object1', }, object2: { id: 'object2', }, object3: { id: 'object3', }, }; const order = ['object1', 'object3']; const selector = (...args) => args.reduce((prev, curr) => ({...prev, [curr.id]: curr}), {}); createSelector( state => state.order, order => createSelector( ...order.map(id => state => state.list[id]), selector, ) ); 

The string ...order.map(id => state => state.list[id]), will distribute the objects as arguments. They will be the same if the array order is not changed. Thus, I can generate a new Object only with the objects listed in the order.

The evaluation function of the first creation selector is called only when the order array is changed. If this happens, recounting the result is in any case necessary. So this is normal. The second only recounts if it receives new values. Values ​​are functions that are generated from an array of orders. Even if the list object changes (due to more entries or smaller changes in other objects that are not taken into account in the current list), the pointer to the objects of the order array remains the same. Thus, we always get the same objects as arguments for our second evaluation function. This prevents unwanted updates.

+4
source

EDIT

I slept and dreamed (seriously haha) with a simple (but perhaps still unnecessary) solution for this case. You must return the primitive type of the filter result so that you can JSON.stringify() it and JSON.parse() on the second selector. The following test package passes:

 const state = { list: [ { id: 1, active: true }, { id: 2, active: false }, { id: 3, active: false }, { id: 4, active: false } ] } const getFilteredListIds = createSelector( (state) => JSON.stringify(state.list.filter((object) => !!object.active)), (filteredList) => JSON.parse(filteredList).map((object) => object.id) ) expect(getFilteredListIds.recomputations()).toEqual(0) expect(getFilteredListIds(state)).toEqual([1]) expect(getFilteredListIds.recomputations()).toEqual(1) expect(getFilteredListIds(state)).toEqual([1]) expect(getFilteredListIds.recomputations()).toEqual(1) const newState = { list: [ ...state.list, { id: 5, active: false } // should not change subset ] } expect(getFilteredListIds(newState)).toEqual([1]) // subset didn't change expect(getFilteredListIds.recomputations()).toEqual(1) // pass :) 

However, depending on your use case, it may be slower than a filter for each call. If you test this performance, share with us.


FIRST MAIL

As I said in the comments , the way you did this makes createSelector useless.

 const state = { list: [ { id: 1, active: true }, { id: 2, active: false }, { id: 3, active: false }, { id: 4, active: false } ] } const getFilteredListIds = createSelector( (state) => state.list.filter((object) => !!object.active), (filteredList) => filteredList.map((object) => object.id) ) expect(getFilteredListIds.recomputations()).toEqual(0) expect(getFilteredListIds(state)).toEqual([1]) expect(getFilteredListIds.recomputations()).toEqual(1) expect(getFilteredListIds(state)).toEqual([1]) expect(getFilteredListIds.recomputations()).toEqual(1) // fail 

First, I made some adaptations to solve this first problem.

 const state = { list: [ { id: 1, active: true }, { id: 2, active: false }, { id: 3, active: false }, { id: 4, active: false } ] } const getList = (state) => state.list // it makes filter only happen if state.list changes const getFilteredList = createSelector( getList, (list) => list.filter((object) => !!object.active) ) const getFilteredListIds = createSelector( getFilteredList, (filteredList) => filteredList.map((object) => object.id) ) expect(getFilteredListIds.recomputations()).toEqual(0) expect(getFilteredListIds(state)).toEqual([1]) expect(getFilteredListIds.recomputations()).toEqual(1) expect(getFilteredListIds(state)).toEqual([1]) expect(getFilteredListIds.recomputations()).toEqual(1) // everything pass 

Now your question is correct:

Is it possible to get only a new object, if there are changes in the objects or the filtered list?

What do you want, is that right?

 const newState = { list: [ ...state.list, { id: 5, active: false } // should not change subset ] } expect(getFilteredListIds(newState)).toEqual([1]) // subset didn't change expect(getFilteredListIds.recomputations()).toEqual(1) // fail 

But the last line will not work, because recomputations() will be 2.

The only way I can see this is to make a memoized filteredList part of your state, but it can be weird.

-2
source

All Articles