The searchStore manages the search interface state, including the search query text and the currently active content tab.
Store Import
import searchStore from "../stores/searchStore.js";
import { useStore } from "@nanostores/react";
// In a React component
function SearchBar() {
const { search, activeTab } = useStore(searchStore);
// ...
}
The searchStore is exported as a default export, unlike playerStore and appStore which use named exports.
State Shape
The searchStore is a simple map store with two properties:
The current search query entered by the user. Empty string when no search is active.
The currently selected content filter tab. Determines which type of search results to display.Possible values:
"all" - Show all result types
"tracks" - Show only track results
"artists" - Show only artist results
"albums" - Show only album results
"playlists" - Show only playlist results
Updating State
Since searchStore is a Nanostores map, you can update individual properties using setKey():
import searchStore from "../stores/searchStore.js";
// Update search query
searchStore.setKey("search", "The Beatles");
// Change active tab
searchStore.setKey("activeTab", "artists");
// Clear search
searchStore.setKey("search", "");
searchStore.setKey("activeTab", "all");
Reading State
You can read the current state using the get() method or subscribe in React components:
// Get current state (one-time read)
const currentState = searchStore.get();
console.log(currentState.search); // "The Beatles"
console.log(currentState.activeTab); // "artists"
// Subscribe to changes in React
import { useStore } from "@nanostores/react";
function SearchComponent() {
const { search, activeTab } = useStore(searchStore);
return (
<div>
<p>Searching for: {search}</p>
<p>Filter: {activeTab}</p>
</div>
);
}
Usage Examples
import searchStore from "../stores/searchStore.js";
import { useStore } from "@nanostores/react";
function SearchBar() {
const { search } = useStore(searchStore);
const handleSearchChange = (e) => {
searchStore.setKey("search", e.target.value);
};
return (
<input
type="text"
value={search}
onChange={handleSearchChange}
placeholder="Search for songs, artists, albums..."
/>
);
}
Tab Filter Component
import searchStore from "../stores/searchStore.js";
import { useStore } from "@nanostores/react";
function SearchTabs() {
const { activeTab } = useStore(searchStore);
const tabs = [
{ id: "all", label: "All" },
{ id: "tracks", label: "Tracks" },
{ id: "artists", label: "Artists" },
{ id: "albums", label: "Albums" },
{ id: "playlists", label: "Playlists" },
];
const handleTabChange = (tabId) => {
searchStore.setKey("activeTab", tabId);
};
return (
<div className="tabs">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => handleTabChange(tab.id)}
className={activeTab === tab.id ? "active" : ""}
>
{tab.label}
</button>
))}
</div>
);
}
Search Results Component
import searchStore from "../stores/searchStore.js";
import { useStore } from "@nanostores/react";
import { useState, useEffect } from "react";
function SearchResults() {
const { search, activeTab } = useStore(searchStore);
const [results, setResults] = useState(null);
useEffect(() => {
// Fetch search results when search or activeTab changes
if (search.trim()) {
fetchSearchResults(search, activeTab).then(setResults);
} else {
setResults(null);
}
}, [search, activeTab]);
if (!search.trim()) {
return <p>Enter a search query to see results</p>;
}
if (!results) {
return <p>Loading...</p>;
}
return (
<div>
{activeTab === "all" && (
<>
<TrackResults tracks={results.tracks} />
<ArtistResults artists={results.artists} />
<AlbumResults albums={results.albums} />
</>
)}
{activeTab === "tracks" && <TrackResults tracks={results.tracks} />}
{activeTab === "artists" && <ArtistResults artists={results.artists} />}
{activeTab === "albums" && <AlbumResults albums={results.albums} />}
</div>
);
}
Debounced Search
import searchStore from "../stores/searchStore.js";
import { useStore } from "@nanostores/react";
import { useState, useEffect } from "react";
function DebouncedSearchBar() {
const { search } = useStore(searchStore);
const [inputValue, setInputValue] = useState(search);
// Debounce the search store update
useEffect(() => {
const timer = setTimeout(() => {
searchStore.setKey("search", inputValue);
}, 300);
return () => clearTimeout(timer);
}, [inputValue]);
return (
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Search..."
/>
);
}
Complete Example
import searchStore from "../stores/searchStore.js";
import { useStore } from "@nanostores/react";
function SearchInterface() {
const { search, activeTab } = useStore(searchStore);
const handleClearSearch = () => {
searchStore.setKey("search", "");
searchStore.setKey("activeTab", "all");
};
return (
<div className="search-interface">
<div className="search-header">
<input
type="text"
value={search}
onChange={(e) => searchStore.setKey("search", e.target.value)}
placeholder="Search for music..."
/>
{search && (
<button onClick={handleClearSearch}>Clear</button>
)}
</div>
<div className="search-tabs">
{["all", "tracks", "artists", "albums", "playlists"].map((tab) => (
<button
key={tab}
onClick={() => searchStore.setKey("activeTab", tab)}
className={activeTab === tab ? "active" : ""}
>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
</button>
))}
</div>
<div className="search-results">
{search ? (
<p>Showing {activeTab} results for "{search}"</p>
) : (
<p>Start typing to search...</p>
)}
</div>
</div>
);
}