{"id":19987799,"url":"https://github.com/mawrkus/lab-filter-bar-ui","last_synced_at":"2026-05-07T01:33:26.548Z","repository":{"id":191889254,"uuid":"458986151","full_name":"mawrkus/lab-filter-bar-ui","owner":"mawrkus","description":"🧪 Lab project: a filter bar UI component","archived":false,"fork":false,"pushed_at":"2023-09-03T12:48:29.000Z","size":2817,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-12T11:46:26.474Z","etag":null,"topics":["filterbar","react","semantic-ui","tvmaze-api","ui","xstate"],"latest_commit_sha":null,"homepage":"https://mawrkus.github.io/lab-filter-bar-ui/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mawrkus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-02-14T02:08:29.000Z","updated_at":"2023-09-03T12:51:09.000Z","dependencies_parsed_at":"2025-01-12T11:42:48.374Z","dependency_job_id":"1bec9c88-86d1-4672-b0c6-1860e0c30042","html_url":"https://github.com/mawrkus/lab-filter-bar-ui","commit_stats":null,"previous_names":["mawrkus/lab-filter-bar-ui"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mawrkus%2Flab-filter-bar-ui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mawrkus%2Flab-filter-bar-ui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mawrkus%2Flab-filter-bar-ui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mawrkus%2Flab-filter-bar-ui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mawrkus","download_url":"https://codeload.github.com/mawrkus/lab-filter-bar-ui/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241425549,"owners_count":19960890,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["filterbar","react","semantic-ui","tvmaze-api","ui","xstate"],"created_at":"2024-11-13T04:38:36.143Z","updated_at":"2026-05-07T01:33:21.515Z","avatar_url":"https://github.com/mawrkus.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🧪 Lab project: a Filter Bar UI component\n\nAn experiment to build a filter bar UI component with:\n\n- [React + hooks](https://reactjs.org/)\n- [Semantic UI](https://react.semantic-ui.com/)\n- [TVmaze API](https://www.tvmaze.com/api)\n- A custom state machine implementation inspired by [XState](https://xstate.js.org/)\n\nThe code is not 100% production-ready (it's an experiment) but I believe that, as it is, it gives a pretty good idea on how to build this type of complex UI component.\n\n[Live demo](https://mawrkus.github.io/lab-filter-bar-ui/)\n\n## ⛩️ Architecture: core concepts\n\n- State machine with domain context (the app state)\n- The app state is observable\n- The Filter Bar receives the app state as props\n- The Filter Bar renders \"dumb\" UI components (Chiclet, ...)\n- Suggestions are fetched from an HTTP client → API (+ cancellable requests)\n- Clear contract on the data structure: suggestion item from the API → filter object → component prop\n- Main states:\n  - `idle` (waiting for user input),\n  - `attribute suggestions` (loading and displaying)\n  - `operator suggestions` (loading and displaying)\n  - `value suggestions` (loading and displaying)\n  - `logical operator suggestions` (loading and displaying)\n- Proxy states (handle logic when editing/next suggestion):\n  - `edit filter suggestions` (when a filter is edited)\n  - `next suggestions` (check for partial filter every time a filter has been created, edited or deleted)\n\n### State diagrams\n\nMade with https://mermaid.js.org/\n\n#### Idle state\n\n```mermaid\nstateDiagram-v2\n    direction LR\n    [*]     --\u003e idle\n    idle    --\u003e displayAttributeSuggestions: startInput (no filters)\n    idle    --\u003e displayLogicalOperatorSuggestions: startInput (last filter is complete and != logical operator)\n    idle    --\u003e proxyToNextSuggestions: startInput (last filter incomplete or logical operator)\n    idle    --\u003e proxyToEditSuggestions: editFilter (!= parens)\n    idle    --\u003e displayAttributeSuggestions: editFilter (empty parens)\n    idle    --\u003e displayLogicalOperatorSuggestions: editFilter (last filter in parens is complete and != logical operator)\n    idle    --\u003e proxyToNextSuggestions: editFilter (last filter in parens incomplete or logical operator)\n    idle    --\u003e proxyToNextSuggestions: removeFilter\n    idle    --\u003e idle: removeLastFilter\n```\n\n#### displayAttributeSuggestions state\n\n```mermaid\nstateDiagram-v2\n    displayAttributeSuggestions: displayAttributeSuggestions\n    note left of displayAttributeSuggestions: Starts loading attributes\n    state if_editing \u003c\u003cchoice\u003e\u003e\n    displayAttributeSuggestions --\u003e if_editing: attributesLoaded\n    if_editing                  --\u003e setAttribute: not editing\n    if_editing                  --\u003e editAttribute: editing\n    displayAttributeSuggestions --\u003e idle: discardSuggestions\n\n    setAttribute                --\u003e idle: discardSuggestions\n    setAttribute                --\u003e displayAttributeSuggestions: selectItem (parens)\n    setAttribute                --\u003e proxyToNextSuggestions: selectItem (search text)\n    setAttribute                --\u003e displayOperatorSuggestions: selectItem (!= search text)\n    setAttribute                --\u003e idle: removeLastFilter\n    editAttribute               --\u003e idle: discardSuggestions\n    editAttribute               --\u003e proxyToNextSuggestions: selectItem\n```\n\n#### displayOperatorSuggestions state\n\n```mermaid\nstateDiagram-v2\n    displayOperatorSuggestions: displayOperatorSuggestions\n    note left of displayOperatorSuggestions: Starts loading operators\n    state if_editing \u003c\u003cchoice\u003e\u003e\n    displayOperatorSuggestions  --\u003e if_editing: operatorsLoaded\n    if_editing                  --\u003e setOperator: not editing\n    if_editing                  --\u003e editPartialOperator: editing partial\n    if_editing                  --\u003e editOperator: editing complete\n    displayOperatorSuggestions  --\u003e idle: discardSuggestions\n\n    setOperator                --\u003e idle: discardSuggestions\n    setOperator                --\u003e displayOperatorSuggestions: selectItem (parens)\n    setOperator                --\u003e proxyToNextSuggestions: selectItem (search text)\n    setOperator                --\u003e displayOperatorSuggestions: selectItem (!= search text)\n    setOperator                --\u003e idle: removeLastFilter\n    editOperator               --\u003e idle: discardSuggestions\n    editOperator               --\u003e proxyToNextSuggestions: selectItem\n    editPartialOperator        --\u003e proxyToNextSuggestions: selectItem\n```\n\n#### displayValueSuggestions state\n\n...\n\n## 📗 Use cases\n\nLaunch and check the [end-to-end](cypress/e2e) tests: `npm run e2e:dev`\n\n### Creation\n\nUsing the mouse or the keyboard:\n\n1. Creation of a 3-part filter\n2. Creation of a logical operator\n3. Creation of search text filter\n4. Creation of a 3-part filter with search text\n5. Creation of 2-part filter\n\n### Edition\n\nUsing the mouse or the keyboard:\n\n- Edition of each part of the filters\n- Changing a 3-part filter to a 2-part one and vice-versa\n- Same with a partial attribute\n- Same with a partial attribute and operator\n- Changing a filter operator from \"single\" to \"multi\" type\n\n### Deletion\n\nUsing the mouse or keyboard:\n\n- Deletion of complete filters\n- Deletion of a partial filter\n\n### Loading\n\n- Request cancellation\n- Error: selection blocked, search text creation allowed\n\n### Parens\n\n- ...\n\n## 🙈 Quirks/Issues\n\n### Invalid state transitions\n\nChoosing \"Season\" then the \"IN\" operator:\n\n- sends a `selectItem` event with type = \"multiple-value\"\n- transitions from `setOperator` → `displayValueSuggestions`\n- sends a `startInput` event (!)\n- 💥 which triggers an invalid transition `displayValueSuggestions` → `undefined`\n- (...then a bit later) sends a `valueSuggestionsLoaded` event\n- (...then a bit later) everything is all good\n\nWhy? Because the `startInput` event comes from:\n\n- unmounting `\u003cSuggestionDropdownSingle /\u003e`\n- mounting `\u003cSuggestionDropdowMulti /\u003e` with `open` = true\n- which calls the `onOpen` prop\n- which sends the `startInput` event\n\n### Dropdown component warnings\n\n- Choose \"Season\" then the \"IN\" operator\n- Select 1 and 4\n- Remove 1\n- Close the dropdown\n\n```text\nWarning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.\n    at Dropdown (http://localhost:3000/static/js/bundle.js:52590:29)\n```\n\nAfter this:\n\n- Change the \"IN\" operator for the \"IS NULL\" operator\n- The filters are properly updated but the chiclet is not re-rendered\n\n### Editing IN/NOT IN values with a partial filter present\n\nThe items of the multi-dropdown are selected on close.\nWhen editing a multi dropdown and closing it by clicking on the document:\n\n- the mouse down closes the multi dropdown,\n- the partial suggestions dropdown is open and\n- 💥 the mouse up closes it immediately (node_modules/semantic-ui-react/src/modules/Dropdown/Dropdown.js L160)\n\n..resulting in poor UX :/\n\nWe've tried to introduce a 100ms delay in the `proxyToNextSuggestions` state to prevent the\ndropdown that opens for the partial filter to be closed on mouse up but it produced wrong dropdown\nplacements when editing completed filter values and a partial filter is present.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmawrkus%2Flab-filter-bar-ui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmawrkus%2Flab-filter-bar-ui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmawrkus%2Flab-filter-bar-ui/lists"}