Reputation: 622
I'm creating a Todo App as an exercise in learning PostgreSQL and functional programming in javascript. The idea is there can be multiple containers each with separate filter settings, each reading from a master list of Todos, then each container displays it's results based on it's filter settings. This is really all done in React-Redux, and Nodejs, but I made the examples below to better illustrate the problem.
The Data:
// Example todos
let a = {
added: "2021-02-01T05:00:00.000Z",
completed: null,
description: "This is just a test.",
name: "testing",
tags: [],
todo_id: 3,
}
let b = {
added: "2021-02-01T05:00:00.000Z",
completed: null,
description: "This is just a test.",
name: "testing",
tags: ["one", "two"],
todo_id: 4
}
let c = {
added: "2021-02-01T05:00:00.000Z",
completed: null,
description: "This is just a test.",
name: "tesin",
tags: ["one"],
todo_id: 5
}
let d = {
added: "2021-02-01T05:00:00.000Z",
completed: '2021-03-01T05:00:00.000Z',
description: "This is just a test.",
name: "testing",
tags: ["two", "testing"],
todo_id: 6
}
// Example filter_group
// This is just to show what the initial state looks like in my React app
const filter_groups = {
"main": {
byTag: 'two',
byName: '',
byCompleted: true
}
}
// Object.keys(filter_groups).map() passes the filter settings down as props
// Just stuck it in a variable for the example
const filter = filter_groups['main']
// Putting example Todos in array
const todo_list = [a,b,c,d]
The current solution is pretty simple, ugly, and inefficient. It applies each filter one at a time:
// ------- CURRENT SOLUTION -------
// if user is trying to filter byCompleted, then filter todo_list
// else do nothing todo_list and pass it to the next filter
// (similar thing applies down the chain)
const byCompleted = filter.byCompleted === true ?
todo_list.filter ( (t) => {
return t.completed !== null
})
:
todo_list
//IF user is trying to filter byTag...
const byTag = filter.byTag !== '' ?
// THEN filter the byCompleted array above
byCompleted.filter( (t) => {
// IF there are tags on the Todo...
let matches = t.tags ?
// THEN filter for matches
t.tags.filter( (tag) => {
return tag === filter.byTag
})
:
// ELSE make this an empty array
[]
// return any Todos with match
return matches.length > 0
})
:
byCompleted
// IF user is trying to filter byName...
const byName = filter.byName !== '' ?
// THEN filter byTag Todos
byTag.filter( (t) => {
return t.name === P.filter.byName
})
:
byTag
console.log(byName);
I really want to know if there's a way to apply the entire filter all at once. In the example you'll see the filter is set to byTag: 'two', byComplete: true
which makes the console print out only one result. My past attempts at getting things to work with one filter pass would return everything with the two
tag.
I tried looking at the documentation and saw there's more going on in the filter function, but it just didn't click, and I couldn't find any examples that fit my use case very well.
Edit: Here's it all on codesandbox: https://codesandbox.io/s/recursing-almeida-cdz65?file=/src/index.js
Upvotes: 0
Views: 157
Reputation: 14165
You mentioned function programming in your question.
The code below takes a currying approach to solving your filtering problems.
Once the individual filters are established, it is a simple matter of chaining them together. Each filter function will return an array for the next function to operate upon.
Note: I had to infer some details.
let a = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "testing", tags: [], todo_id: 3, }
let b = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "testing", tags: ["one", "two"], todo_id: 4 }
let c = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "tesin", tags: ["one"], todo_id: 5 }
let d = { added: "2021-02-01T05:00:00.000Z", completed: '2021-03-01T05:00:00.000Z', description: "This is just a test.", name: "testing", tags: ["two", "testing"], todo_id: 6 }
const filter_groups = { "main": { byTag: 'two', byName: '', byCompleted: true } }
const filter = filter_groups['main']
const todo_list = [a,b,c,d]
let byTag = td => td.tags.includes(filter.byTag);
let byName = td => filter.name ? td.tags.includes(filter.name) : true;
let byCompleted = td => filter.byCompleted ? td.completed !== null : false ;
let result = todo_list.filter(byTag).filter(byName).filter(byCompleted);
console.log(result);
EDIT: OP asked to do this in one code block. That solution follows:
let a = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "testing", tags: [], todo_id: 3, };
let b = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "testing", tags: ["one", "two"], todo_id: 4 };
let c = { added: "2021-02-01T05:00:00.000Z", completed: null, description: "This is just a test.", name: "tesin", tags: ["one"], todo_id: 5 };
let d = { added: "2021-02-01T05:00:00.000Z", completed: '2021-03-01T05:00:00.000Z', description: "This is just a test.", name: "testing", tags: ["two", "testing"], todo_id: 6 };
const todo_list = [a,b,c,d];
const filter_groups = { "main": { tags: 'two', name: '', completed: true } };
let result2 = todo_list.filter(td=>{
return Object.entries(filter_groups['main'])
.filter(([k,v])=>!!v!=false)
.map(f=>!!td[f[0]])
.every(f=>f==true);
});
console.log(result2);
The OP also mentioned a concern about performance in his original code and my "three step" solution.
In order to offer confidence to just code the way you normally would, I ran a series of tests using jsben.ch.com. The image below shows the OP's original code consistently coming in at #2 or #1. The "three step" solution shares that result. The bottom line is that more steps doesn't necessarily equate to poorer performance. If you can get the site to cooperate, run the tests several times to get an idea of the differences in performance. JSBEN.CH Tests
Take these results with a grain of salt - good for showing comparisons but not much else.
Upvotes: 1