Fuse Integration with Reflex
Use Fuse.js for lightweight, client-side fuzzy search inside your Reflex web apps. This guide shows how to integrate it using CDN, connect it to Reflex state, and build a responsive search UI with results.
Overview
This guide demonstrates integrating Fuse.js fuzzy search into a Reflex application using client-side state management and custom JavaScript.
Project Structure
You need three files:
app.py- Main Reflex applicationassets/list.json- Search dataassets/fuse.js- Search logic
Data Format
Your list.json should contain an array of searchable objects:
[ { "title": "Battle for Natural", "author": { "firstName": "Emily", "lastName": "Cain" } } ]
Search Script Setup
The fuse.js file initializes Fuse.js and exposes search functions globally:
// Fuse.js configuration (note: no require() since we load via CDN) const fuseOptions = { keys: ["title", "author.firstName", "author.lastName"], // Added lastName includeScore: true, threshold: 0.3, // Adjust for search sensitivity }; let fuse; // This will store the Fuse instance // Initialize Fuse.js with data function initializeFuse(list) { try { fuse = new Fuse(list, fuseOptions); console.log("Fuse.js initialized successfully with", list.length, "items"); } catch (error) { console.error("Error initializing Fuse.js:", error); } } // Perform a search function searchFuse(query) { console.log("searchFuse called with query:", query); if (!fuse) { console.warn("Fuse.js not initialized yet"); return []; } if (!query || query.trim() === "") { return []; } try { const results = fuse.search(query); console.log('Search results for "' + query + '":', results); return results; } catch (error) { console.error("Error during search:", error); return []; } } window.initializeFuse = initializeFuse; window.searchFuse = searchFuse; console.log("Search functions defined and attached to window");
Client State Management
Define client-side state variables for reactive updates:
from reflex.experimental import ClientStateVar docs = ClientStateVar.create("docs", default=[], global_ref=True) query = ClientStateVar.create("query", default="", global_ref=True)
Type Definitions
Structure your data types for type safety:
class AuthorDict(TypedDict): firstName: str lastName: str class ItemDict(TypedDict): title: str author: AuthorDict class DocDict(TypedDict): item: ItemDict refIndex: int score: float
Loading External Resources
Inject Fuse.js CDN and custom scripts into the document head:
def fuse_cdn_script(): return rx.script(src="https://cdn.jsdelivr.net/npm/fuse.js@7.1.0") def custom_search_script(): return rx.script(src="/fuse/fuse.js") def load_json_file_and_initialize(): return rx.script(''' fetch('/fuse/list.json') .then(response => response.json()) .then(data => { setTimeout(() => { window.initializeFuse(data); window.fuseInitialized = true; }, 2000); }); ''')
Search Input Component
Create a debounced search input that updates client state:
def search_input(): return rx.box( rx.icon(tag="search", size=14), rx.el.input( id="search_input", on_change=rx.call_script(''' (() => { if (!window._debouncedSearch) { let timeout; window._debouncedSearch = function () { clearTimeout(timeout); timeout = setTimeout(() => { const inputValue = document.getElementById("search_input").value; const results = window.searchFuse(inputValue); refs._client_state_setDocs(results); refs._client_state_setQuery(inputValue); }, 300); }; } window._debouncedSearch(); })(); '''), auto_focus=True, placeholder="Search documentation ...", ), )
Rendering Results
Display search results with conditional rendering:
def search_content(): return rx.scroll_area( rx.box( rx.cond( query.value.to(str).strip() != "", rx.cond( docs.value.to(List[DocDict]).length() > 0, rx.foreach( docs.value.to(List[DocDict]), lambda doc: rx.box( rx.el.p(rx.el.strong("Title: "), doc["item"]["title"]), rx.el.p(rx.el.strong("Author: "), doc["item"]["author"]["firstName"] + " " + doc["item"]["author"]["lastName"]), rx.el.p(rx.el.strong("Score: "), f"{doc['score']:.6f}"), ), ), no_results_found(), ), ), ), )
Dialog Integration
Wrap the search in a dialog with keyboard shortcut trigger:
def fuse_search_front_end_only(): return rx.dialog.root( rx.dialog.trigger(search_trigger()), rx.dialog.content( search_input(), search_content(), ), )
Application Setup
Register scripts in the app initialization:
app = rx.App( head_components=[ fuse_cdn_script(), custom_search_script(), load_json_file_and_initialize(), ], )
Key Concepts
Client state variables enable reactive updates without server round-trips. The debounce pattern prevents excessive search calls during typing. Fuse.js handles fuzzy matching with configurable sensitivity via the threshold option. Results include match scores for relevance ranking.
Customization
Adjust threshold in fuseOptions (0.0 = exact match, 1.0 = match anything). Modify keys array to search different fields. Change debounce timeout (300ms default) for responsiveness vs performance tradeoffs.