简体   繁体   中英

I'm trying to filter an array of Objects simultaneosly by number and by string value

i have a little problem in my App ! it's a simple App that shows some restaurant tables and let you filter these tables by number of people eating (capacity) and the preferred place (Inside, Patio, Bar)

I have a problem specifically with this filter functionality: i'm able to filter the tables by Place, but when i try to simultaneosly filter the Tables by Capacity it won't work (it'll forget the Place filter, and just filter them by Capacity). I'll explain the user flow i used:

  1. I click on the Place select and choose the option 'Patio'
  2. At this point 2 tables are showed; one is for 5 people, the other for 9 people
  3. i click on the Capacity filter, choosing a table for 9 persons.
  4. the App will forget the 'Patio' value and it'll also show a table with Location ' Bar' (and that's the part i'm trying to fix, i want the App to remember 'Patio' and only showing that one)
     const defaultState : any = {
            tables: [],
            tablesFiltered: [],
            bookings: [],
            error: null,
            loading: false,
        }
         case FILTER_TABLE:
                        return {
                            ...state,
                            tablesFiltered: action.payload,
                        }

this is my action

export const filterTables = (filteredTable: tableI[]) => {
    return (dispatch: (arg0: { type: string; payload?: unknown; }) => void) =>
    dispatch({type: FILTER_TABLE, payload: filteredTable})
}

finally, the component where the logic of the filter is:

TableFilter.tsx

import { Form } from 'react-bootstrap';
import { useSelector, useDispatch } from 'react-redux';
import { filterTables } from '../../store/actions';
import { tableI } from '../../Interfaces';

const TableFilter: React.FC = () => {
  const tables: tableI[] = useSelector((state: any) => state.tables.tables);

  const dispatch = useDispatch();

  const handleChange = (event: any) => {
    const locationFilter: string = event.target.value;
    const capacityFilter: any = event.target.value;

    // console.log(typeof capacityFilter);

    const filteredArr = tables.filter(
      (table) => table.location === locationFilter
    );

    // console.log(filteredArr);
    dispatch(filterTables(filteredArr));
  };

  const changeCapacity = (event: any) => {
    // const locationFilter: string = event.target.value;
    const capacityFilter: any = event.target.value;

    // console.log(typeof capacityFilter);

    const filteredArr = tables.filter(
      (table) => table.capacity >= Number.parseInt(capacityFilter)
    );

    // console.log(filteredArr);
    dispatch(filterTables(filteredArr));
  };

  const locations: string[] = tables.map((table) => table.location);
  const capacity: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9];

  const uniqueLocations = [...new Set(locations)];
return (
    <Form>
      <Form.Group className="mb-3" controlId="formBasicEmail">
        <Form.Label>Select a table</Form.Label>
        <Form.Control
          onChange={handleChange}
          as="select"
          aria-label="form-select-location"
        >
          <option>Select your table's location</option>
          {uniqueLocations &&
            uniqueLocations.map((location: string, index: number) => (
              <option aria-label="location" key={index} value={location}>
                {location}
              </option>
            ))}
        </Form.Control>
      </Form.Group>

      <Form.Group className="mb-3" controlId="formBasicEmail">
        <Form.Label>Select the capacity of your table</Form.Label>
        <Form.Control
          onChange={changeCapacity}
          as="select"
          aria-label="form-select-capacity"
        >
          <option>Number of persons sitting </option>
          {capacity &&
            capacity.map((capacity: number, index: number) => (
              <option aria-label="capacity" key={index} value={capacity}>
                {capacity}
              </option>
            ))}
        </Form.Control>
      </Form.Group>
    </Form>
  );
};

export default TableFilter;

here's my GitHub repo:

https://github.com/miki-miko/booking-system-testing

You could take an array of all filters and filter the data by all filters, you actually have.

For example take two filters, like

capacity = n => ({ capacity }) => capacity >= n;
place = p => (place) => place === p;

filters = [
    capacity(2),
    place('Inside')
]

result = data.filter(o => filters.every(fn => fn(o)));

You should filter the data in your selector since it is derived data and you should not store such data in your redux state. Here is a simple example of how you could do this:

 const { Provider, useDispatch, useSelector } = ReactRedux; const { createStore, applyMiddleware, compose } = Redux; const { createSelector } = Reselect; const initialState = { tables: ['A', 'B', 'C', 'D', 'E'].flatMap((place) => [...new Array(5)].map((_, index) => ({ place, capacity: index + 1, })) ), //not sure if filter needs to be here, is is shared // by multiple components in your application? filter: { capacity: 'all', place: 'all', }, }; //action types const SET_FILTER = 'SET_FILTER'; //action creators const setFilter = (key, value) => ({ type: SET_FILTER, payload: { key, value }, }); const reducer = (state, { type, payload }) => { if (type === SET_FILTER) { const { key, value } = payload; return { ...state, filter: { ...state.filter, [key]: value, }, }; } return state; }; //selectors const selectTables = (state) => state.tables; const selectFilter = (state) => state.filter; const selectPlaces = createSelector( [selectTables], (tables) => [...new Set(tables.map(({ place }) => place))] ); const selectCapacities = createSelector( [selectTables], (tables) => [ ...new Set(tables.map(({ capacity }) => capacity)), ] ); //return true if value is all, this is specific to the filer value // if the value is "all" then return true //When passing a function to this it returns a function that takes a value // when calling that function with a value it returns a function that // takes an item const notIfAll = (fn) => (value) => (item) => value === 'all' ? true : fn(value, item); //specific filter const capacityBiggerThan = notIfAll( //could do more abstraction here with getProp, and re usable compare // functions but leave this out for simplicity (value, item) => item.capacity >= Number(value) ); const isPlace = notIfAll( (value, item) => value === item.place ); //Apply multiple filter functions, it receives an array of filter functions // and returns a function that receives an item as parameter, when the item // is passed to this function it will call all filter functions passing this item const mergeFilters = (filterFunctions) => (item) => filterFunctions.every((filterFunction) => filterFunction(item) ); //select filtered data const selectFilterData = createSelector( [selectTables, selectFilter], (tables, { place, capacity }) => { return tables.filter( mergeFilters([ isPlace(place), capacityBiggerThan(capacity), ]) ); } ); //creating store with redux dev tools const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore( reducer, initialState, composeEnhancers( applyMiddleware( () => (next) => (action) => next(action) ) ) ); const FilterSelect = React.memo(function FilterSelect({ filterKey, value, values, }) { const dispatch = useDispatch(); return ( <select value={value} onChange={(e) => dispatch(setFilter(filterKey, e.target.value)) } > <option value="all">all</option> {values.map((place) => ( <option key={place} value={place}> {place} </option> ))} </select> ); }); const App = () => { const { capacity, place } = useSelector(selectFilter); const places = useSelector(selectPlaces); const capacities = useSelector(selectCapacities); const filterData = useSelector(selectFilterData); return ( <div> <FilterSelect filterKey="place" value={place} values={places} /> <FilterSelect filterKey="capacity" value={capacity} values={capacities} /> <div> <h1>result</h1> <pre> {JSON.stringify(filterData, undefined, 2)} </pre> </div> </div> ); }; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script> <div id="root"></div>

The code may be confusing since there are functions that get a function passed to it and return a function that when called with a value will return yet another function. You could rewrite the filter in the following way but it would not be re usable and more difficult to extend:

const selectFilterData = createSelector(
  [selectTables, selectFilter],
  (tables, { place, capacity }) => {
    //just put all logic in one function, easier to read but repeating logic
    //  and more difficult to maintain if many values are used or rules change
    return tables.filter((item) => {
      const placeFilter =
        place === 'all' ? true : item.place === place;
      const capacityFilter =
        capacity === 'all'
          ? true
          : item.capacity >= Number(capacity);
      return placeFilter && capacityFilter;
    });
  }
);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM