Skip to content

Commit 9b81232

Browse files
committed
[FEATURE]: Refactor React Native search bar to use react-instantsearch-core with Typesense adapter
1 parent fb9a975 commit 9b81232

1 file changed

Lines changed: 61 additions & 77 deletions

File tree

docs-site/content/guide/react-native-search-bar.md

Lines changed: 61 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,21 @@ npx create-expo-app@latest typesense-react-native-search-bar --template blank-ty
141141

142142
This will scaffold a new React Native project with TypeScript support using Expo.
143143

144+
Once your project scaffolding is ready, navigate to the project directory and install the required dependencies:
145+
146+
```shell
147+
cd typesense-react-native-search-bar
148+
npm install
149+
npm i react-instantsearch-core typesense-instantsearch-adapter
150+
```
151+
152+
Let's go over the key dependencies:
153+
154+
- **react-instantsearch-core** - Provides InstantSearch hooks and components for React Native
155+
- **typesense-instantsearch-adapter** - Connects InstantSearch with Typesense
156+
144157
:::tip Note
145-
Unlike web frameworks, React Native doesn't use the Typesense JavaScript client or InstantSearch.js directly. Instead, we'll make direct API calls to Typesense using the Fetch API, which is more suitable for mobile applications.
158+
React Native can use `react-instantsearch-core` with the `typesense-instantsearch-adapter`, just like web frameworks. This gives you access to powerful InstantSearch hooks and widgets. Alternatively, you can make direct API calls to Typesense using the Fetch API for a lighter implementation.
146159
:::
147160

148161
## Project Structure
@@ -233,10 +246,8 @@ Let's create the project structure step by step. After each step, we'll show you
233246
authors: string[];
234247
image_url: string;
235248
publication_year: number;
236-
}
237-
238-
export interface Document {
239-
document: Book;
249+
average_rating?: number;
250+
ratings_count?: number;
240251
}
241252
```
242253

@@ -265,31 +276,28 @@ Let's create the project structure step by step. After each step, we'll show you
265276
Add this to `utils/typesense.ts`:
266277

267278
```typescript
268-
import { Document } from "../types/Book";
269-
270-
export const search = async (searchQuery: string): Promise<Document[]> => {
271-
const url = `${process.env.EXPO_PUBLIC_TYPESENSE_PROTOCOL}://${process.env.EXPO_PUBLIC_TYPESENSE_HOST}:${process.env.EXPO_PUBLIC_TYPESENSE_PORT}/collections/books/documents/search?q=${encodeURIComponent(
272-
searchQuery,
273-
)}&query_by=title,authors`;
274-
275-
const response = await fetch(url, {
276-
method: "GET",
277-
headers: {
278-
"X-TYPESENSE-API-KEY": process.env.EXPO_PUBLIC_TYPESENSE_API_KEY || "xyz",
279-
"Content-Type": "application/json",
280-
},
281-
});
282-
283-
if (!response.ok) {
284-
throw new Error("Typesense search failed");
285-
}
286-
287-
const data = await response.json();
288-
return data?.hits || [];
289-
};
279+
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";
280+
281+
const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
282+
server: {
283+
apiKey: process.env.EXPO_PUBLIC_TYPESENSE_API_KEY || "xyz",
284+
nodes: [
285+
{
286+
host: process.env.EXPO_PUBLIC_TYPESENSE_HOST || "localhost",
287+
port: Number(process.env.EXPO_PUBLIC_TYPESENSE_PORT) || 8108,
288+
protocol: process.env.EXPO_PUBLIC_TYPESENSE_PROTOCOL || "http",
289+
},
290+
],
291+
},
292+
additionalSearchParameters: {
293+
query_by: "title,authors",
294+
},
295+
});
296+
297+
export const searchClient = typesenseInstantsearchAdapter.searchClient;
290298
```
291299

292-
This utility function makes a direct HTTP request to the Typesense server. Unlike web frameworks that use InstantSearch.js, React Native apps typically use the Fetch API for better performance and smaller bundle sizes on mobile devices. However, this also means that if you want advanced features like debouncing, you'll need to implement them manually.
300+
This utility file creates the Typesense InstantSearch adapter, which bridges Typesense with InstantSearch. The adapter handles all the communication with Typesense and provides a search client that works seamlessly with `react-instantsearch-core` hooks.
293301

294302
6. Create the component files:
295303

@@ -324,25 +332,18 @@ Let's create the project structure step by step. After each step, we'll show you
324332
```typescript
325333
import React from "react";
326334
import { StyleSheet, TextInput } from "react-native";
335+
import { useSearchBox } from "react-instantsearch-core";
327336

328-
interface SearchInputProps {
329-
value: string;
330-
onChangeText: (text: string) => void;
331-
placeholder?: string;
332-
}
337+
export const SearchInput = () => {
338+
const { query, refine } = useSearchBox();
333339

334-
export const SearchInput = ({
335-
value,
336-
onChangeText,
337-
placeholder,
338-
}: SearchInputProps) => {
339340
return (
340341
<TextInput
341342
style={styles.searchInput}
342-
placeholder={placeholder}
343+
placeholder="Search books..."
343344
placeholderTextColor="#999"
344-
value={value}
345-
onChangeText={onChangeText}
345+
value={query}
346+
onChangeText={refine}
346347
/>
347348
);
348349
};
@@ -361,7 +362,7 @@ Let's create the project structure step by step. After each step, we'll show you
361362
});
362363
```
363364

364-
The `TextInput` component is React Native's equivalent of an HTML input field. The `onChangeText` prop automatically handles text changes, making it simpler than web's `onChange` event.
365+
This component uses the `useSearchBox` hook from `react-instantsearch-core` to connect the search input to Typesense. The `query` value and `refine` function are provided by InstantSearch, which handles debouncing and search state management automatically.
365366

366367
8. Create the `BookCard` component in `components/BookCard.tsx`:
367368

@@ -434,69 +435,50 @@ Let's create the project structure step by step. After each step, we'll show you
434435

435436
```typescript
436437
import React from "react";
437-
import { Document } from "../types/Book";
438+
import { useHits } from "react-instantsearch-core";
438439
import { BookCard } from "./BookCard";
440+
import { Book } from "../types/Book";
441+
442+
export const BookList = () => {
443+
const { hits } = useHits<Book>();
439444

440-
export const BookList = ({ books }: { books: Document[] }) => {
441445
return (
442446
<>
443-
{books.map((book) => (
444-
<BookCard key={book.document.id} book={book.document} />
447+
{hits.map((book) => (
448+
<BookCard key={book.id} book={book} />
445449
))}
446450
</>
447451
);
448452
};
449453
```
450454

451-
This simple component maps over the search results and renders individual book cards.
455+
This component uses the `useHits` hook from `react-instantsearch-core` to automatically receive search results from Typesense. The hook provides the `hits` array which updates in real-time as the user types in the search box.
452456

453457
10. Finally, update your `App.tsx` to bring everything together:
454458

455459
```typescript
456460
import { StatusBar } from "expo-status-bar";
457-
import { useEffect, useState } from "react";
458461
import { StyleSheet, View, ScrollView } from "react-native";
459462
import {
460463
SafeAreaProvider,
461464
useSafeAreaInsets,
462465
} from "react-native-safe-area-context";
466+
import { InstantSearch } from "react-instantsearch-core";
467+
import { Heading } from "./components/Heading";
463468
import { BookList } from "./components/BookList";
464-
import { Document } from "./types/Book";
465-
import { search } from "./utils/typesense";
466469
import { SearchInput } from "./components/SearchInput";
470+
import { searchClient } from "./utils/typesense";
467471
468472
function AppContent() {
469-
const [books, setBooks] = useState<Document[]>([]);
470-
const [searchQuery, setSearchQuery] = useState("");
471473
const insets = useSafeAreaInsets();
472474
473-
useEffect(() => {
474-
fetchBooks().catch((error) => {
475-
console.error("Error fetching books:", error);
476-
});
477-
}, []);
478-
479-
useEffect(() => {
480-
fetchBooks().catch((error) => {
481-
console.error("Error fetching books:", error);
482-
});
483-
}, [searchQuery]);
484-
485-
async function fetchBooks() {
486-
const data = await search(searchQuery);
487-
setBooks(data);
488-
}
489-
490475
return (
491476
<View style={[styles.container, { paddingTop: insets.top }]}>
492477
<ScrollView>
493-
<SearchInput
494-
value={searchQuery}
495-
onChangeText={setSearchQuery}
496-
placeholder="Search books..."
497-
/>
478+
<Heading />
479+
<SearchInput />
498480
<View style={styles.grid}>
499-
<BookList books={books} />
481+
<BookList />
500482
</View>
501483
<StatusBar style="auto" />
502484
</ScrollView>
@@ -507,7 +489,9 @@ Let's create the project structure step by step. After each step, we'll show you
507489
export default function App() {
508490
return (
509491
<SafeAreaProvider>
510-
<AppContent />
492+
<InstantSearch searchClient={searchClient} indexName="books">
493+
<AppContent />
494+
</InstantSearch>
511495
</SafeAreaProvider>
512496
);
513497
}
@@ -526,7 +510,7 @@ Let's create the project structure step by step. After each step, we'll show you
526510
});
527511
```
528512

529-
This main component uses React hooks (`useState`, `useEffect`) to manage search state and trigger searches when the query changes. The `SafeAreaProvider` and `useSafeAreaInsets` ensure the app respects device-specific safe areas like notches and home indicators.
513+
This main component wraps the app with the `InstantSearch` provider, which connects all the search components to Typesense. The `searchClient` from our utility file and the `indexName` ("books") are passed to the provider. The `SafeAreaProvider` ensures the app respects device-specific safe areas like notches and home indicators.
530514

531515
11. Your final project structure should look like this:
532516

0 commit comments

Comments
 (0)