Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keeping data object after filtering

Small Background intro

I´m currently working the User Administration page of my project and running into a small problem here. I have a table which contains some material-ui`s Usercard. For Each user that uses my System exist´s one card. The card´s are generated from data that comes from my database and then written into a redux store.

The Admin can do several interactions with the database that changes some Userdata. To provide an easy way to find a specific user a <TextField /> was implemented that filter´s the table of Usercards.

All of the things mentioned here works!

The Problem

As mentioned in the Intro the data are stored in a redux store. When I filter the data, an action is dispatched

    export const FILTER_ALL_USER_BY_NAME = "FILTER_ALL_USER_BY_NAME"
    export const FILTER_ALL_USER_BY_DEPARTMENT = "FILTER_ALL_USER_BY_DEPARTMENT"

    export default function filterAllUser(filter, filterOption){
        return (dispatch, getState) => {
            if(filterOption === 'name'){
                return dispatch (filterUserByName(filter))
            }else{
                return dispatch (filteUserByDepartment(filter))
            }
        }
    }

    function filterUserByName(filter){
        return {
            type: FILTER_ALL_USER_BY_NAME,
            filter: filter
        }
    }

    function filteUserByDepartment(filter){
        return {
            type: FILTER_ALL_USER_BY_DEPARTMENT,
            filter: filter
        }
    }

The Reducer

Even if the reducers works, it is the main reason for my problem.

Why?

It is because, when I filter the data I was not able to really filter the state, rather then return a new state object which leads me to the problem that the allUserData and filteredUserData get out of sync after the userdata are changed.

Let me explain this in code

    function allUser(state = {allUserData: []}, action){
        switch (action.type){
            case 'REQUEST_ALL_USER':
                return Object.assign({}, state, {
                    isFetching: true
                });
            case 'RECEIVE_ALL_USER':
                return Object.assign({}, state, {
                    isFetching: false,
                    allUserData: action.items
                });
            case 'FILTER_ALL_USER_BY_NAME':
                return Object.assign({}, state, {
                    filteredUserData: state.allUserData.filter(user => user.userNameLast.toLowerCase().indexOf(action.filter.toLowerCase()) >= 0)
                });
            case 'FILTER_ALL_USER_BY_DEPARTMENT':
                return Object.assign({}, state, {
                    filteredUserData: state.allUserData.filter(user => user.departmentName.toLowerCase().indexOf(action.filter.toLowerCase()) >= 0)
                });
            default:
                return state
        }
    }

But when I´m trying to filter the original state and the user removes the filter, the data that didn´t matched the filter are gone.

    case 'FILTER_ALL_USERS': return allUsers.filter(user => user.userNameLast.toLowerCase().indexOf(action.filter.toLowerCase()) >= 0);

How can I filter the state, but keep the data ?

like image 941
el solo lobo Avatar asked Aug 15 '26 02:08

el solo lobo


2 Answers

As requested, I have put together some code for you. It would be something like below.

As a side note, I would pass the filter as { field: 'userLastName', text: your filter text} as the filter criteria. Or to make it even more scalable, you can pass a filter-handler instead of text above. That way you can have any type of filter, and not just text filters.

export default function allUser(state = {allUserData: [], filters: {}, filteredUserData: []}, action){
  switch (action.type){
    case 'REQUEST_ALL_USER':
      return {
        ...state,
        isFetching: true
      };
    case 'RECEIVE_ALL_USER':
      return {
        ...state,
        isFetching: false,
        allUserData: action.items,
        filteredUserData: filterData(action.items, state.filters),
      };
    case 'FILTER_ALL_USER_BY_NAME':
    {
      const updatedFilters = {
        ...state.filters,
        userNameLast: action.filter
      }
      return {
        ...state,
        filteredUserData: filterData(state.allUserData, updatedFilters),
        filters: updatedFilters
      };
    }
    case 'FILTER_ALL_USER_BY_DEPARTMENT':
    {
      const updatedFilters = {
        ...state.filters,
        departmentName: action.filter
      }
      return {
        ...state,
        filteredUserData: filterData(state.allUserData, updatedFilters),
        filters: updatedFilters
      };
    }
    default:
      return state
  }
}

const filterData = (users, filters) => {
  return users.filter(filterFn(filters));
};

const filterFn = filters => item => Object.keys(filters).reduce((res, filter) => {
  return res && item[filter].toLowerCase().indexOf(filters[filter].toLowerCase()) >= 0;
}, true);

Unit tests

import usersReducer from './users';

describe('usersReducer', () => {
  describe('RECEIVE_ALL_USER', () => {
    const RECEIVE_ALL_USER = 'RECEIVE_ALL_USER';
    it('should replace users in state', () => {
      const initialState = { isFetching: false, allUserData: [{ name: 'A' }], filters: {}};
      const newUsers = [{ name: 'B' }];
      const newState = { ...initialState, allUserData: newUsers, filteredUserData: newUsers};
      expect(usersReducer(initialState, { type: RECEIVE_ALL_USER, items: newUsers })).toEqual(newState);
    })
  })
  describe('FILTER_ALL_USER_BY_NAME', () => {
    let FILTER_ALL_USER_BY_NAME = 'FILTER_ALL_USER_BY_NAME';
    it('should filter users by name', () => {
      const initialState = { isFetching: false, allUserData: [{ userNameLast: 'Doe' }, { userNameLast: 'Smith' }], filters: {}};
      const filterText = 'd';
      const finalState = { isFetching: false,
        allUserData: [{ userNameLast: 'Doe' }, { userNameLast: 'Smith' }],
        filters: { userNameLast: filterText },
        filteredUserData: [{ userNameLast: 'Doe' }]
      };

      expect(usersReducer(initialState, { type: FILTER_ALL_USER_BY_NAME, filter: filterText})).toEqual(finalState);
    })
  })
  describe('FILTER_ALL_USER_BY_DEPARTMENT', () => {
    let FILTER_ALL_USER_BY_DEPARTMENT = 'FILTER_ALL_USER_BY_DEPARTMENT';
    it('should filter users by department', () => {
      const initialState = { isFetching: false, allUserData: [{ departmentName: 'IT' }, { departmentName: 'Human Resources' }], filters: {}};
      const filterText = 'it';
      const finalState = {
        ...initialState,
        filters: { departmentName: filterText },
        filteredUserData: [{ departmentName: 'IT' }]
      };

      expect(usersReducer(initialState, { type: FILTER_ALL_USER_BY_DEPARTMENT, filter: filterText})).toEqual(finalState);
    })
  })
});
like image 183
Abhishek Jain Avatar answered Aug 17 '26 15:08

Abhishek Jain


Best for these cases is to some selector library, example reselect. Instead of editing the original state create selectors for sorting and filter and pass the result to component.

There's also a quite similar example in reselect documentation https://github.com/reactjs/reselect#selectorstodoselectorsjs.

like image 32
Risto Novik Avatar answered Aug 17 '26 17:08

Risto Novik