el solo lobo el solo lobo - 3 months ago 6
React JSX Question

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 ?

Answer

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);
    })
  })
});