Problem
I create this menu filter component:
with this menu, in practice, you can filter on a cards list feeling free to choose different filters at the same time; the cards list returned will be the results of all options selected.
to generate this kind of filter I filter on filtered option and so on, in other words like so:
return props.tasks.filter(x => {
if (props.key.includes('task-status')) {
return props.status.some(status => x['task-status'] === status)
}
return true
}).filter(x => {
if (props.key.includes('task-priority')) {
return props.priority.reduce((vres, val) => vres.concat(val), []).some(priority => x['task-priority'] === priority)
}
return true;
}).filter(x => {
if (props.key.includes('task-actual-owner')) {
return props.users.some(user => x['task-actual-owner'] === user)
}
return true;
}).filter(x => {
if (props.key.includes('task-expiration-time')) {
if (x['task-expiration-time'] === null) {
return false;
}
const f = props.expiration.flat();
return x['task-expiration-time']['java.util.Date'] >= f[0] && x['task-expiration-time']['java.util.Date'] <= f[1]
}
return true;
})
So the entire list of cards come from this array of objects:
array = [
{
"task-id": 142,
"task-name": "Task",
"task-subject": "",
"task-description": "",
"task-status": "Ready",
"task-priority": 0,
"task-is-skipable": false,
"task-actual-owner": null,
"task-created-by": null,
"task-created-on": {
"java.util.Date": 1606322625000
},
"task-activation-time": {
"java.util.Date": 1606322625000
},
"task-expiration-time": {
"java.util.Date": 1200832320000
},
"task-proc-inst-id": 187,
"task-proc-def-id": "businessProcess.main",
"task-container-id": "businessProcess_1.0.1-SNAPSHOT",
"task-parent-id": -1,
"correlation-key": "187",
"process-type": 1
},
{
"task-id": 141,
"task-name": "Task",
"task-subject": "",
"task-description": "",
"task-status": "InProgress",
"task-priority": 9,
"task-is-skipable": false,
"task-actual-owner": "john.doe",
"task-created-by": null,
"task-created-on": {
"java.util.Date": 1606322577000
},
"task-activation-time": {
"java.util.Date": 1606322577000
},
"task-expiration-time": {
"java.util.Date": 1674200580000
},
"task-proc-inst-id": 186,
"task-proc-def-id": "businessProcess.main",
"task-container-id": "businessProcess_1.0.1-SNAPSHOT",
"task-parent-id": -1,
"correlation-key": "186",
"process-type": 1
},
{
"task-id": 140,
"task-name": "Task",
"task-subject": "",
"task-description": "",
"task-status": "Reserved",
"task-priority": 6,
"task-is-skipable": false,
"task-actual-owner": "peter.griffin",
"task-created-by": null,
"task-created-on": {
"java.util.Date": 1606322524000
},
"task-activation-time": {
"java.util.Date": 1606322524000
},
"task-expiration-time": {
"java.util.Date": 1863598080000
},
"task-proc-inst-id": 185,
"task-proc-def-id": "businessProcess.main",
"task-container-id": "businessProcess_1.0.1-SNAPSHOT",
"task-parent-id": -1,
"correlation-key": "185",
"process-type": 1
},
{
"task-id": 139,
"task-name": "Task",
"task-subject": "",
"task-description": "",
"task-status": "Reserved",
"task-priority": 0,
"task-is-skipable": false,
"task-actual-owner": "homer.simpson",
"task-created-by": null,
"task-created-on": {
"java.util.Date": 1606322446000
},
"task-activation-time": {
"java.util.Date": 1606322446000
},
"task-expiration-time": null,
"task-proc-inst-id": 184,
"task-proc-def-id": "businessProcess.main",
"task-container-id": "businessProcess_1.0.1-SNAPSHOT",
"task-parent-id": -1,
"correlation-key": "184",
"process-type": 1
},
{
"task-id": 138,
"task-name": "Task",
"task-subject": "",
"task-description": "",
"task-status": "Reserved",
"task-priority": 0,
"task-is-skipable": false,
"task-actual-owner": "jim.carrey",
"task-created-by": null,
"task-created-on": {
"java.util.Date": 1606322412000
},
"task-activation-time": {
"java.util.Date": 1606322412000
},
"task-expiration-time": null,
"task-proc-inst-id": 183,
"task-proc-def-id": "businessProcess.main",
"task-container-id": "businessProcess_1.0.1-SNAPSHOT",
"task-parent-id": -1,
"correlation-key": "183",
"process-type": 1
}
]
and the array of objects created by the multiple options choose can be like this (for example):
[
{
key: "task-status",
label: "Waiting For",
status: "Ready"
},
{
key: "task-status",
label: "Picked Up",
status: "Reserved"
}
{
key: "task-priority",
label: "Low",
priority: [0, 1, 2, 3, 4]
},
{
key: "task-actual-owner",
label: "John Doe",
owner: "john.doe"
},
{
key: "task-expiration-time",
expiration: [1611598726000, 1611609526000]
}
];
All works properly but I’m trying to find a way to refactor the first function…(return props.tasks.filter(x => { ...
)
Is there a way to achieve this?
Solution
There are two main things you can do to clean it up a bit:
- Instead of chaining a bunch of
.filter()
s together, create a list of the filter functions you wish to use, then only use the selected ones. - You can break up the function a bit by pulling out and naming each of the filter functions. This can make it easier to understand at a glance what logic your function is doing.
Here’s one way to code it up after applying those two ideas.
(The following is untested, but should give an idea)
function applyFilters(props) {
const filters = [];
if (props.key.includes('task-status')) {
filters.push(createFilterFor.status(props));
}
if (props.key.includes('task-priority')) {
filters.push(createFilterFor.priority(props));
}
if (props.key.includes('task-actual-owner')) {
filters.push(createFilterFor.owner(props));
}
if (props.key.includes('task-expiration-time')) {
filters.push(createFilterFor.expirationTime(props));
}
return props.tasks.filter(combineFilters(filters));
}
const combineFilters = filters => (
entry => filters.every(filter => filter(entry))
);
const createFilterFor = {
status: ({ status }) => (
task => status.some(statusText => task['task-status'] === statusText)
),
priority: ({ priority }) => (
task => priority.flat(1).some(priorityText => task['task-priority'] === priorityText)
),
owner: ({ users }) => (
task => users.some(user => task['task-actual-owner'] === user)
),
expirationTime: ({ expiration }) => (
task => {
if (!task['task-expiration-time']) return false;
const taskExpiration = task['task-expiration-time']['java.util.Date'];
const [start, end] = expiration.flat();
return taskExpiration >= start && taskExpiration <= end;
}
),
};
If you find yourself adding a lot of filter functions that follow this pattern, you could add a layer of abstraction that allows you to remove the if-then chain, but this makes the code more unreadable and less flexible.
Update
Based on @DiddyO’s feedback, I think I understand what the goal of this question is better. Here’s an updated, much more simplified version that should do what was asked (assuming I understood the comment right).
(Once again, this is untested and may have a bug or two, but it should give an idea of how to go about accomplishing this task)
const applyFilters = props => (
props.tasks.filter(createFilterFromProps(props))
);
const createFilterFromProps = props => (
entry => Object.entries(filters).every(
([key, filter]) => !props.key.includes(key) || filter(props, entry[key])
)
);
const filters = {
'task-status': ({ status }, taskStatus) => status.some(statusText => taskStatus === statusText),
'task-priority': ({ priority }, taskPriority) => priority.flat(1).some(priorityText => taskPriority === priorityText),
'task-actual-owner': ({ users }, actualOwner) => users.some(user => user === actualOwner),
'task-expiration-time': ({ expiration }, taskExpiration) => {
if (!taskExpiration) return false;
const [start, end] = expiration.flat();
return taskExpiration['java.util.Date'] >= start && taskExpiration['java.util.Date'] <= end;
},
};
```