Redux Selectors - Filter and then sort using selectors

932 Views Asked by At

In my Redux store I have a list of notes. I wish to

  1. Filter out the notes that are created by me
  2. sort those notes by created date / title / etc.

I would like to separate logic for filtering and sorting into own functions to make the code more easy to maintain.

I have read the tutorial on redux-reselect and have come up with the below structure.

import { createSelector } from 'reselect';

const sortNotesBy = ({notes, sortBy}) => {
  return notes.sort((a, b) => {
    if (sortBy === TITLE) {
      //dueDate date - createdAt
      return a.dueDate > b.dueDate ? -1 : 1
    }
    else if (sortBy === CREATED_DATE) {
      //createdAt
      return a.createdAt > b.createdAt ? -1 : 1
    }
  })
}

const getMyNotes = ({notes, uid}) => {
  return notes.filter(note => note.author === uid)
}

const sortBy = props.sortBy // set by mapStateToProps
const uid = props.uid // set by mapStateToProps
const notesSelector = state => state.notes;

// Filter out my notes
export const myNotes = createSelector([notesSelector], notes => {
  return getMyNotes({notes, uid})
});

// Sort my notes
export const sortedNotes = createSelector([myNotes], notes => {
  return sortNotesBy({notes, sortBy})
});

Is the above approach of "stacking" selectors a good approach to accomplish code separation, or might there be any issues with this approach?

Kind regards /K

2

There are 2 best solutions below

0
On
    function sortObjectsByField(fieldAlias) {
    return function(a, b) {
        return a[fieldAlias] > b[fieldAlias] ? -1 : 1
    }
}
const FIELD_YOU_NEED = 'FIELD_YOU_NEED'
array.sort(sortObjectsByField(FIELD_YOU_NEED))

I am using this function for my purposes, so you can provide this function right into Array.sort and provide any field alias you need. In my opinion this is good for maintaining and also, you dont need to provide an array to your sorting fucntion.

0
On

My initial code does not work fully - I cannot access sortBy and uid from variables. These values can however be read from store by stacking selectors.

const sortBy = props.sortBy // set by mapStateToProps
const uid = props.uid // set by mapStateToProps

The result would look something like

import { createSelector } from 'reselect';

// INPUT SELECTORS
const getFilters = state => (state.filters) ? state.filters : []
const getUser= state => (state.user) ? state.user : []
const getNotes = state => (state.notes) ? state.notes : []

// SELECTORS
//Get my sort preference
export const getMySortBy = createSelector(
  [getNotes, getFilters], 
  (notes, filters) => {
    const openNoteId = filters?.id ?? ''
    const noteObj = notes.find((not)=>not.id === openNoteId )
    const mySortBy = notObj?.personalSortOrder ?? TITLE // Default to title sort
    return mySortBy
  }
)

//Get my uid
export const getMyUid = createSelector(
  [getUser],
  (user) => { return user?.uid ?? '' }
)

// Get my notes
export const getMyNotes = createSelector(
  [getMyUid, getNotes],
  (uid, notes) => {
    return notes.filter(note => note.author === uid)
  }
)

const sortNotesBy = ({notes, sortBy}) => {
  return notes.sort((a, b) => {
    if (sortBy === TITLE) {
      //dueDate date - createdAt
      return a.dueDate > b.dueDate ? -1 : 1
    }
    else if (sortBy === CREATED_DATE) {
      //createdAt
      return a.createdAt > b.createdAt ? -1 : 1
    }
  })
}
    
// Sort my notes
export const sortedNotes = createSelector(
  [getMyNotes, getMySortBy], 
  (notes, sortBy) => {
    return sortNotesBy({notes, sortBy})
  }
);

I found that by using reselect and stacking selectors, my code is easier to follow and maintain, since each piece of logic can be tested and maintained separately.

Kind regards /K