Description
This is it. The big finale to FF's MVP: Type Filtering.
As the user scrolls around on the map, they would like to filter by one or more types. The types that are available to filter directly reflects the types that are currently visible on the map. This means that you can filter in cluster view or location view. It also means that you should GET /types.json
each time the map scrolls, and provide the NE/SW lat/lng of the viewport as well; the API will handle giving you the types that have at least one location inside the viewport, as well as their respective counts.
Currently on fallingfruit.org, the website only supports filtering by at most one type. Our app will offer filtering by any number of types. Note that the backend already supports this by taking an array of types; it's just that the current website does not, because UI is tricky.
We have decided to solve this problem with a hierarchal tree select, using this library: react-dropdown-tree-select.
Additionally, types are represented in a hierarchal format. For now, Ethan has chosen to limit the depth of the hierarchy to 1, so assume that for now:
![image](https://user-images.githubusercontent.com/4369024/111063578-8578d880-847d-11eb-84c8-3a424a2ee76d.png)
The API data gives this tree by specifying a parent_id
for each type in GET /types/{id}.json
. You will have to build this tree in order to show the hierarchy, because the tree select library expects data in the form of nested objects. (CS 225, anyone?)
![image](https://user-images.githubusercontent.com/4369024/111063697-30899200-847e-11eb-8109-2c57bb0d54f1.png)
Right now, it seems that rebuilding the tree after the map scrolls would be slow because parent_id
is currently not included in GET /types.json
, which means you'd have to GET /types/{id}.json
for each type, which fallingfruit.org definitely does not do. @ezwelty, does the current app get the type hierarchy from somewhere? How would we do that as well? See bottom of issue for more details.
Another difference we'll have is the type counts. On fallingfruit.org, the count of the parent type is not the sum of the counts of the child types:
![image](https://user-images.githubusercontent.com/4369024/111064570-cb846b00-8482-11eb-999b-f26ae29be394.png)
Notice how 0 != 2 + 12
. It's this way because the count on the parent actually represents the number of locations that are categorized as just "Maple", and not a specific kind of maple. (Let's call them uncategorized maples.) When you click on it, however, it seems that the website does explicitly select the parent and its children (all maples). You'll see that the count also updates to the sum 14.
This is unintuitive, and will also not conceptually work with our tree select, since it's implied that clicking on the parent type will select all the child types. We've decided to solve this by moving the uncategorized maples to a child category "Other" under the general type Maple (see designs below). That "Other" will own the uncategorized count, and the parent type will then have the sum of its children's counts.
Filtering is implemented by sending an array of type IDs to the backend in requests such as GET /locations.json
and GET /types.json
. We currently have a React context called SearchContext
that conveys the viewport of selected search results to MapPage
, so it can pan to the searched location. You can take advantage of that context to sync the list of currently filtered type IDs to MapPage
as well, so it can include those in its GET /locations.json
requests.
We also use React context for sharing the current viewport of the map across components. That's in MapContext
's view
.
Searching for types
There are tons of types in FF, so it's critical that the user can search for them while filtering. Luckily, the library we're using provides searching built-in. Note these options:
keepTreeOnSearch: By default the tree gets flattened when you're searching, but we want the tree to stay.
Styling
Unlike Reach UI, the library we're using doesn't have as
props to specify what components to use for checkboxes, inputs, etc. So we will likely have to replicate styles from our existing UI components, without being able to use the components themselves.
Make sure to reference react-dropdown-tree-select's styling tips, specifically the default class names so that you can select them while styling the outer component.
Relevant Figma designs
Mobile:
![image](https://user-images.githubusercontent.com/4369024/111064727-a6dcc300-8483-11eb-915c-f0b1a07f7ebe.png)
Desktop:
![image](https://user-images.githubusercontent.com/4369024/111064722-9e848800-8483-11eb-855f-5f5aa9175674.png)
Suggested branch name: type-filtering
Suggested PR title (as would probably be suggested by @code-arman): <some relevant emoji> Add type filtering
Refs #23
Learning objectives
- Using React context to pass data around. We do this in order to avoid drilling props back and forth in a complicated component hierarchy. This one is pretty valuable, since some kind of structured state management is essential in larger apps. Don't worry, context is pretty easy to use, without a lot of boilerplate. (Remember how we said we wouldn't use Redux in our project? We're still not, but Redux is actually implemented with React contexts.)
- Data structures and building a tree in JavaScript. Who would have thought?
- Styling, styling, styling
- Breaking a complex feature down into simple parts, and implementing each part cleanly
Todo
- Since this is a big feature, let Jeffrey know about what he can help out on by sometime mid-week, if you think it's necessary
- Adding a "pressed" prop to
IconButton
- Reorganizing the shared Search component that is currently in desktop and mobile to include the filter button and the tree select component. In that shared component, at most one of [search suggestions, type filtering] should be visible. Note that it's best to just wrap the existing
Search
component so it doesn't get too long
- Building the tree with the type hierarchy into a format that the tree select will expect
- In particular, ensuring that type counts are correct and adding the "Other" category to each parent type
- Loading types data based on the current viewport of the map, from
MapContext
- Upon changes in the tree select, propagate the correct list of types to the map via
SearchContext
- Being careful with the "Other" category
- Adding type search to the tree select
- Style the tree select nodes
- Style the tree select search input
Useful links
Additional notes
Type Hierarchy
- One simple solution is to multiple requests is to include
parent_id
in GET /types.json
so we don't have to make additional GET requests for each type. Rebuilding the actual tree will probably be very fast compared to the overhead of making the network request, so this should work fine for now.
- Another solution, which @ezwelty mentioned and requires a lot more work, is to change the API to only return type IDs throughout all requests, and expect the frontend to get all type information ahead of time. This may speed up the API a bit, but requires more investigation.
Virtualization
The library we're using now supports virtualization out of the box, which means that DOM elements are only rendered as they scroll into view. Definitely great to have.