2

I have a Reselect selector that maps an array of chosen ids into objects from a normalized store.

const activeObjectsSelector = createSelector(
  state => state.activeIds,
  state => state.objects.byId,
  (activeIds, objectsById) => activeIds.map(id => objectsById[id])
)

The problem is that it busts the cache and re-runs anytime new objects are added to the normalized store, because the value of state.objects.byId changes. The only change I care about busting the cache is the value of state.activeIds. Is this possible?

Ryan Giglio
  • 1,085
  • 1
  • 14
  • 26
  • I haven't used reselect that extensively, but I think this thread might be helpful to take a look through: https://github.com/reduxjs/reselect/issues/441 It might not be pretty, but I imagine you could accomplish what you want by writing a customMemoize. – shannon May 22 '20 at 01:29

2 Answers2

0

You can use defaultMemoize of reselect for this to memoize the result of the map by passing it to a memoized function that will take the items of the array as arguments:

const { createSelector, defaultMemoize } = Reselect;
const state = {
  activeIds: [1, 2],
  objects: {
    1: { name: 'one' },
    2: { name: 'two' },
    3: { name: 'three' },
  },
};
const selectActiveIds = (state) => state.activeIds;
const selectObjects = (state) => state.objects;
const createMemoizedArray = () => {
  const memArray = defaultMemoize((...array) => array);
  return (array) => memArray.apply(null, array);
};
const createSelectAcitiveObjects = () => {
  const memoizedArray = createMemoizedArray();
  return createSelector(
    selectActiveIds,
    selectObjects,
    (ids, objects) =>
      memoizedArray(ids.map((id) => objects[id]))
  );
};
const selectAcitiveObjects = createSelectAcitiveObjects();
const one = selectAcitiveObjects(state);
console.log('one is:',one);
const two = selectAcitiveObjects(state);
console.log('one is two', two === one);
const newState = { ...state, activeIds: [1, 2, 3] };
const three = selectAcitiveObjects(newState);
console.log('three is two', three === two);
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>


<div id="root"></div>
HMR
  • 37,593
  • 24
  • 91
  • 160
0

Here is a snippet for two arguments in different slices:

Slice A: jobSlice

export const rtkAllJobs = (state:RootState) => state.jobs.allJobs;

export const allJobRelatedCountsTest = createSelector(
  [rtkMyUserSettings, rtkAllJobs],
  (myUserSettings, allJobs) => {
    
  const myMatches = myUserSettings ? allJobs.filter((job: Job) => job.isJobActive && +job.jobCumulativeScore > 0) : [];
  let myMatchesCount = myUserSettings === undefined ? 0 : myUserSettings?.likedJobsArr! === null ? 0 : myMatches.length;
  
  let allRelatedCounts = {
    liked: myUserSettings === undefined ? 0 : myUserSettings.likedJobsArr === null ? 0 : myUserSettings?.likedJobsArr!.filter((likedJob: LikedJob) => likedJob.isJobActive).length ,
    applied: myUserSettings === undefined ? 0 : myUserSettings.appliedJobsArr === null ? 0 : myUserSettings?.appliedJobsArr!.length, 
    matches:  myMatchesCount,
  }

  return allRelatedCounts;
})

Slice B: individualSlice

export const rtkMyUserSettings = (state:RootState) => state.individual.userSettings;

Component I want to access the data in.

  const jobMatchCount = useAppSelector(allJobRelatedCountsTest)

rtkAllJobs accesses the job slice's value allJobs then is passed into the createSelector([dependencies], (dependencies[0], dependencies[1] ...) => { // do the thing... return results}) rtkMyUserSettings is stored in the individualSlice.tsx file and just grabs the user's setting object which I need to pluck information from.

Nice! Another thing I learned: What if you have an argument that is not part of your redux store that you need to pass in to do stuff?

In my case, it was a translation hook that takes an array of numbers and assigns either english/french.

Below, we pass two arguments from our redux store data and filter + a component based argument called t

// Assuming you have a state structure like this
const initialState = {
  data: {},
  filter: '',
};

// Selector functions
const selectData = (state) => state.data;
const selectFilter = (state) => state.filter;

// Selector factory function
const createSelectFilteredData = (t) => {
  return createSelector(
    [selectData, selectFilter],
    (data, filter) => {
      // Process data using filter...
      const filteredData = // your processing logic here

      // Use the t function
      t(filteredData.name); // Assuming you want to call t with the 'name' property
      return filteredData;
    }
  );
};

// In your component
const MyComponent = () => {

  const t = useTranslateHook(); 

  // Create the memoized selector using the factory function
  const selectFilteredData = createSelectFilteredData(t);

  const filteredData = useSelector(selectFilteredData);

  // Render using filteredData
};
RJA
  • 129
  • 1
  • 7