Adding Local Search History to Bluesky
- Open Source
- React Native
- Software Development
- TypeScript
I recently contributed a persistent search history feature to Bluesky's React Native app. The implementation uses AsyncStorage for data persistence and React's state management for real-time updates, giving users quick access to their previous searches.
Implementation Architecture
The search history feature consists of three main components: persistent storage using AsyncStorage, state management with React hooks, and a UI for displaying and interacting with search history.
Storage Layer with AsyncStorage
AsyncStorage provides persistent, key-value storage that's ideal for managing search history. The implementation uses a simple string array, limiting storage overhead while maintaining quick access times.
const [searchHistory, setSearchHistory] = React.useState<string[]>([])
React.useEffect(() => {
const loadSearchHistory = async () => {
try {
const history = await AsyncStorage.getItem('searchHistory')
if (history !== null) setSearchHistory(JSON.parse(history))
} catch (e) {
logger.error('Failed to load search history', e)
}
}
loadSearchHistory()
}, [])
Managing Search History Updates
The history management system maintains a maximum of five entries with newest items at the front. When the list exceeds five entries, the oldest items are dropped. Each new search term is deduplicated before being added.
async function updateSearchHistory(newQuery: string) {
newQuery = newQuery.trim()
if (newQuery && !searchHistory.includes(newQuery)) {
let newHistory = [newQuery, ...searchHistory]
if (newHistory.length > 5) newHistory = newHistory.slice(0, 5)
setSearchHistory(newHistory)
try {
await AsyncStorage.setItem('searchHistory', JSON.stringify(newHistory))
} catch (e) {
logger.error('Failed to save search history', e)
}
}
}
User Interface Implementation
The UI renders search history as a dropdown beneath the search bar, appearing only when the input is focused and empty. This provides context-aware access to previous searches while keeping the interface clean.
{!query && inputIsFocused && (
<CenteredView style={styles.searchHistoryContainer}>
{searchHistory.length > 0 && (
<View style={styles.searchHistoryContent}>
{searchHistory.map((historyItem) => (
<Pressable
key={historyItem}
onPress={() => handleHistoryItemClick(historyItem)}
style={styles.historyItem}>
<Text style={pal.text}>{historyItem}</Text>
</Pressable>
))}
</View>
)}
</CenteredView>
)}
Event Handling
The click handler updates the query and triggers a new search. Note that onSubmit receives the item directly rather than reading from state, avoiding the async state update timing issue.
function handleHistoryItemClick(item: string) {
setQuery(item)
onSubmit(item)
}
Performance Considerations
The implementation prioritizes performance through several optimizations:
- Limiting history length to prevent memory issues
- Deduplicating entries to avoid redundant storage
- Using React's state management for responsive updates
- Conditional rendering to minimize unnecessary render operations
This feature shipped to Bluesky's production environment and has been well-received. The combination of storage efficiency and UI responsiveness provides a seamless search experience with minimal resource overhead.