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:
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:
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.