【问题标题】:I'm trying to filter an array of Objects simultaneosly by number and by string value我正在尝试按数字和字符串值同时过滤对象数组
【发布时间】:2021-12-19 08:59:51
【问题描述】:

我的应用程序有一个小问题! 这是一个简单的应用程序,可以显示一些餐厅的桌子,并让您根据用餐人数(容量)和首选地点(Inside、Patio、Bar)过滤这些桌子

我对这个过滤器功能有一个特别的问题: 我可以按位置过滤表格,但是当我尝试同时按容量过滤表格时,它将不起作用(它会忘记位置过滤器,而只是按容量过滤它们)。 我将解释我使用的用户流程:

  1. 我点击 Place select 并选择选项“Patio”
  2. 此时显示了 2 个表格;一个是 5 人,另一个是 9 人
  3. 我点击容量过滤器,选择可容纳 9 人的桌子。
  4. 应用程序会忘记“天井”值,它还会显示一个带有位置“栏”的表格(这是我要修复的部分,我希望应用程序记住“天井”并且只显示一)
     const defaultState : any = {
            tables: [],
            tablesFiltered: [],
            bookings: [],
            error: null,
            loading: false,
        }
         case FILTER_TABLE:
                        return {
                            ...state,
                            tablesFiltered: action.payload,
                        }

这是我的行动

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

最后,过滤器的逻辑所在的组件:

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;

这是我的 GitHub 存储库:

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

【问题讨论】:

  • 您想始终按容量位置过滤吗?
  • 是的!我希望应用程序显示按地点/位置过滤的表格,以及容量!所以如果我选择“9”和“Patio”,另一个只有 5 个座位​​的 Patio 表应该会消失

标签: javascript reactjs typescript redux


【解决方案1】:

您可以获取所有过滤器的数组并按所有过滤器过滤数据,您实际上有。

例如取两个过滤器,比如

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

然后将它们放入一个数组中。没关系,如果你只使用一个或多个,如果需要的话。

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

因此,它返回 所有 过滤器返回 true 或至少一个 truthy 返回值的对象。

这个带有Array#every 的对象返回所有条件都为真的对象。

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

如果只有一个约束必须为真,请改用Array#some

result = data.filter(o => filters.some(fn => fn(o))); // exists

【讨论】:

  • 您好,感谢您的回答!我很难理解一些逻辑;我会尝试在我的项目中实现它!
  • @Michelangelo Nina 的代码使用partial application/closure/curry 我在链接中发布的示例使用排序,但与一些 cmets 具有相同的主体。
  • @HMR 谢谢!我仍在尝试弄清楚,因为我对这些概念不熟悉,因此该链接肯定会有所帮助!我尝试将此代码添加到项目中,如下所示:codepen.io/miki-miko/pen/WNEMgeP?editors=1112 但我遇到了“TypeError:fn is not a function”
  • @Michelangelo try: filters[0] = capacityNum(event.target.value)filters[1] 一样,不知道为什么容量和位置都是event.target.value
  • 哦,是的,我这样做是因为最初我想要 2 个不同的功能,因为您实际上单击 2 个不同的选项标签来选择位置和容量!所以我想了两个不同的 OnChange 函数,会更好吗?
【解决方案2】:

您应该过滤选择器中的数据,因为它是派生数据,并且您不应该将此类数据存储在您的 redux 状态中。下面是一个简单的例子来说明如何做到这一点:

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>

代码可能会令人困惑,因为有些函数会获取一个传递给它的函数并返回一个函数,当使用一个值调用该函数时会返回另一个函数。您可以通过以下方式重写过滤器,但它不能重用并且更难以扩展:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-18
    • 1970-01-01
    • 2019-04-04
    • 1970-01-01
    • 1970-01-01
    • 2015-03-24
    • 2021-12-15
    • 2019-12-30
    相关资源
    最近更新 更多