RockOn pt 5: Filtering Data
Published: 2020-07-24 18:15:47

*This is the fifth post in my series about building [RockOn](https://rockonapp.com). Get the full background starting [here](https://dev.to/dianakw8591/rockon-pt-1-using-nokogiri-to-seed-my-database-2h05).*

We've made it to the point in RockOn where the user has entered some of their ascents and has a couple data points saved. What now? Well, a bunch of graphs and filter options of course! 

![stats page view of RockOn](https://dev-to-uploads.s3.amazonaws.com/i/4w2llwapue033csbhqta.png)

In broad strokes, a user's entries are fetched from the database and passed through a filtering function that is controlled by the user. That filtered array is then passed to the graphs and logbook so that only ascents that meet the selected criteria are displayed. Here's a better view of all those filter options:

![filter options](https://dev-to-uploads.s3.amazonaws.com/i/r5bkmab9yvemae6d0o2s.png)

There's a lot of climbing terminology in there but don't let that dissuade you. You can think of these options just like filters for a house search on Zillow: are you looking to rent or buy? What square footage and number of bathrooms do you want? Is in-unit laundry a must have? Yes, rock climbing is just like searching for your next apartment. 

Let's break this down and start from the beginning. When a user logs in, all of their entries are fetched from the back end and saved in state to be passed to other components as needed. The entries come as an array of objects - here's an example of an entry object:
```
{
 climb: {
  area_array: ["International", "Europe", "Spain", 
   "Catalonia", "…],
  climb_id: 110372,
  full_type: "Sport",
  key_type: "Sport",
  name: "La Discórdia",
  numeric_rating: 13,
  rating: "5.10"
 }
 beta: "",
 notes: "very cold and raining...",
 outcome: "Onsight",
 partners: "",
 pitches: 1,
 rack: "",
 start_date: "2019-11-23",
 style: "Lead",
}
```
This object has all the information needed for the graphs and logbooks, and most of the keys are filterable. We'll walk through the process of filtering by outcome - the many ways that climbers describe whether they fell off the climb at some point, or whether they held on until the top. Again, don't worry about the terminology too much - just know that only one outcome can be applied to a given entry.  

In the filter options, everything begins as selected. In the case of outcomes, that means that entries with any of the nine outcome options attached will be displayed.

![outcome filter options](https://dev-to-uploads.s3.amazonaws.com/i/jljwfiyapy0xkr4hh2l7.png)

All of the filter options are just part of a controlled form! 
```react
<Form.Group >
  <Form.Label>Select outcomes:</Form.Label>
  <div key={`inline-checkbox`} className="mb-3">
  {Object.keys(outcome).map(key => (
    <span className={mappingRopeOutcomes[key]} key={key}>
      <Form.Check inline
       type="checkbox"
       onChange={handleOutcomeToggle}
       label={key}
       name={key}
       checked={outcome[key]}
      />
    </span>
  ))}
  </div>
</Form.Group>
```
The `span` surrounding each check box is simply for styling purposes, but the functionality of the form follows the same pattern as discussed in [last week's post](https://dev.to/dianakw8591/rockon-pt-4-controlled-forms-in-react-529l). Any change to the form is handled by the `handleOutcomeToggle` function:
```react
const handleOutcomeToggle = ({ target }) => setOutcome(s => ({ ...s, [target.name]: !s[target.name] }));
```
This function in turn updates state using `setOutcome` and simply flips the boolean from what it was before. The `Stats` component where these functions live is functional, so I am using the `useState` hook to save and set my outcomes:
```react
  const [outcome, setOutcome] = useState({
    'Onsight': true,
    'Flash': true,
    'Redpoint': true,
    'Pinkpoint': true,
    'Tronsight': true,
    'No Falls': true,
    'TR Attempt': true,
    'Repeat': true,
    'Attempt': true
  })
```
Rather than having a `useState` hook for every outcome possibility like this:
```react
const [onsight, setOnisght] = useState(true)
...
```
I can use one hook that handles the entire object, and update each key of the object appropriately by looking at the `target.name` that comes from my form. Nifty!

So following this flow, if I don't want to see any of my climbs with an outcome of 'Onsight', I unselect that checkbox, which changes the value of `outcome.Onsight` to false. 

Next I need a way to use state to filter out any climbs from the entry array that have an outcome of `Onsight`. I'm sure there are lots of ways to solve this filtering problem (and quite a few different helper function to use, both built into Javascript or from outside libraries like [Lodash](https://lodash.com/)) but I'll share my original solution here. In the first step I built an array of outcomes that only included the ones selected, where the value is true.
```react
  const outcomeArray = Object.keys(outcome).filter(key => outcome[key])
```
Here `Object.keys(outcome)` is an array of the outcome keys.  The filter removes any keys that have false values in the original `outcome` object. 

Using `outcomeArray`, I then filtered my entries array such that only entries with outcomes included in the `outcomeArray` are kept:
```react
entries.filter(e => outcomeArray.includes(e.outcome))
```

The filtered array is then passed to the graphs and logbook components, so that as a user select filters the displays are updated in real time. 

This is the same flow I followed for all of the filter options - dates, grades, style, etc. Some of those data types took a bit more manipulation or a slightly different method to achieve the filter but the concept is the same. Finally, by stringing the filter functions together, I was able to apply all of the filters at once. Here's that final function:
```react
  const filtered = entries
    .filter(e => moment(e.start_date) >= moment(start))
    .filter(e => moment(e.start_date) <= moment(end))
    .filter(e => typeArray.includes(e.climb.key_type))
    .filter(e => e.climb.key_type === "Boulder" || styleArray.includes(e.style))
    .filter(e => outcomeArray.includes(e.outcome))
    .filter(e => e.climb.numeric_rating >= grade.low && e.climb.numeric_rating <= grade.high)
```
To plot the `filtered` array even more manipulation is needed - I'll dive into that next week in part 6. Have a great weekend and thanks for reading!