Skip to main content

React SDK

The @viglet/turing-react-sdk provides headless hooks and unstyled UI components for building custom search experiences on top of the Viglet Turing ES Semantic Navigation API.

  • Headless architecture — zero built-in styles, full render control via render props
  • Two modes — manual (programmatic) or URL-synced (URL as source of truth)
  • TypeScript-first — full type safety for all hooks, components, and API responses
  • React 18 & 19 compatible

Storybook — Interactive demos and live examples at turing.viglet.org/react-sdk


Installation

npm install @viglet/turing-react-sdk

Peer dependencies: react (^18 or ^19) and axios (^1.0).


Quick Start

import { TuringProvider, useTuringSearch } from "@viglet/turing-react-sdk";

function App() {
return (
<TuringProvider config={{ site: "my-site", locale: "en_US" }}>
<SearchPage />
</TuringProvider>
);
}

function SearchPage() {
const { documents, searchQuery, status } = useTuringSearch();

return (
<div>
<input
placeholder="Search..."
onKeyDown={(e) => {
if (e.key === "Enter") searchQuery(e.currentTarget.value);
}}
/>
{status === "loading" && <p>Loading...</p>}
{documents.map((doc) => (
<div key={doc.url}>
<h3>{doc.title}</h3>
<p>{doc.description}</p>
</div>
))}
</div>
);
}

TuringProvider

The root context provider that makes Turing config and search state available to all hooks.

import { TuringProvider } from "@viglet/turing-react-sdk";

Props

PropTypeRequiredDescription
configTuringConfigYesSite configuration
urlSync{ searchParams, setSearchParams }NoEnable URL synchronization
childrenReactNodeYesChild components

TuringConfig

{
site: string; // Turing site name (required)
locale?: string; // Default locale (e.g., "en_US")
sort?: string; // Default sort (e.g., "relevance")
}

URL Sync Mode

When urlSync is provided, the URL becomes the single source of truth. Changes to the URL trigger automatic searches, and searches update the URL automatically.

import { useSearchParams } from "react-router-dom";

function App() {
const [searchParams, setSearchParams] = useSearchParams();
return (
<TuringProvider
config={{ site: "my-site", locale: "pt_BR" }}
urlSync={{ searchParams, setSearchParams }}
>
<SearchPage />
</TuringProvider>
);
}

Hooks

useTuringSearch

Core search hook for manual (non-URL) mode. Manages the full lifecycle: query, results, pagination, facets, locale, and sort.

const {
status, // "idle" | "loading" | "success" | "error"
data, // Raw TurSearchResponse
chat, // AI chat response (null for wildcard queries)
documents, // Resolved documents with mapped default fields
groups, // Resolved groups (when group param used)
error, // Error message
params, // Current search params
search, // Execute with given params
searchQuery, // New text query (resets page to 1)
navigate, // Navigate via Turing href (facet, pagination)
changeLocale, // Change locale
changeSort, // Change sort
goToPage, // Go to specific page
} = useTuringSearch(initialParams?);

useTuringAutoComplete

Autocomplete hook that calls the Turing /ac endpoint with built-in debouncing.

const {
suggestions, // string[] — completion suggestions
isLoading, // Fetching state
fetch, // Trigger autocomplete (min 2 chars)
clear, // Clear suggestions
} = useTuringAutoComplete(debounceMs?: number); // default: 300

useTuringFacets

Provides facet data with toggle/clear actions. Requires urlSync mode.

const {
facetGroups, // Groups with toggle/clear actions
hasSelected, // Any facet selected?
clearAll, // Remove all selections
raw, // Raw API facet data
} = useTuringFacets();

Each facetGroup contains:

{
name: string; // Group identifier
label: string; // Localized label
multivalued: boolean;
hasSelected: boolean;
clear: () => void; // Clear group selections
facets: [{
count: number;
label: string;
selected: boolean;
toggle: () => void; // Toggle this facet
}];
}

useTuringPagination

Provides pagination data with navigation helpers. Requires urlSync mode.

const {
pages, // All pages with action helpers
currentPage, // Current page number
pageCount, // Total pages
totalCount, // Total results
hasPages, // Multiple pages?
} = useTuringPagination();

Each page contains:

{
page: number;
label: string; // "1", "2", "Next", etc.
type: string; // PAGE | CURRENT | FIRST | PREVIOUS | NEXT | LAST | ELLIPSIS
isCurrent: boolean;
isEllipsis: boolean;
select: () => void; // Navigate to this page
}

useTuringDocument

Typed, normalized access to document custom fields, eliminating Array.isArray() checks.

const {
getString, // (field, fallback?) => string
getArray, // (field) => string[]
getNumber, // (field, fallback?) => number
getBoolean, // (field) => boolean
getDate, // (field) => Date | null
getRaw, // <T>(field) => T | undefined
fields, // Direct field access
} = useTuringDocument(document);

useTuringTabs

Manages tab-based search with filter switching and grouping. Keeps URLs clean using implicit params.

const {
activeIndex, // Active tab index
activeTab, // Active tab definition
tabs, // All tabs with select() action
isGrouped, // Active tab uses grouping?
documents, // Docs for active tab
groups, // Groups (if grouped)
} = useTuringTabs({
attribute: "templateName",
items: [
{ label: "All" },
{ label: "Courses", filter: "templateName:cursos", group: "category" },
{ label: "News", filter: "templateName:noticias", rows: 5 },
],
});

useTuringSortOptions

Fetches available sort options (built-in + custom from Turing admin console).

const {
sortOptions, // { value: string, label: string }[]
isLoading,
error,
refresh, // Re-fetch options
} = useTuringSortOptions();

useTuringSearchHistory

Persists search terms in IndexedDB, scoped per site + domain.

const {
history, // Recent terms (newest first, deduplicated)
save, // Save a term
remove, // Remove a term
clear, // Clear all for current site
} = useTuringSearchHistory(maxItems?: number); // default: 50

useTuringUrlSearch

Returns the shared search store from TuringProvider when urlSync is configured.

const store = useTuringUrlSearch();
// Same shape as TuringProvider's internal store:
// status, data, documents, groups, params, inputValue,
// submitSearch, navigate, setLocale, setSort, showAll, etc.

UI Components

All components are unstyled (headless). You control the look via render props and CSS.

TuringSearchBar

Simple search bar with customizable input/button via render props.

<TuringSearchBar
onSearch={(query) => console.log(query)}
placeholder="Search..."
defaultQuery=""
renderInput={(props) => <input {...props} className="my-input" />}
renderButton={({ onClick }) => <button onClick={onClick}>Go</button>}
/>
PropTypeDescription
onSearch(query: string) => voidCallback on Enter or button click
defaultQuerystringPre-fill value
placeholderstringInput placeholder
renderInput(props) => ReactNodeCustom input renderer
renderButton(props) => ReactNodeCustom button renderer

TuringSearchField

Advanced compound component with autocomplete, search history, and dropdown.

<TuringSearchField turing={urlSearchStore} debounce={300} maxHistory={8}>
<TuringSearchField.Input placeholder="Search..." className="my-input" />
<TuringSearchField.Button className="my-btn">Search</TuringSearchField.Button>
<TuringSearchField.Dropdown
className="my-dropdown"
historyLabel="Recent searches"
clearAllLabel="Clear all"
renderSuggestion={(term, onClick) => (
<div onClick={onClick}>{term}</div>
)}
renderHistory={(term, onClick, onRemove) => (
<div>
<span onClick={onClick}>{term}</span>
<button onClick={onRemove}>x</button>
</div>
)}
/>
</TuringSearchField>

TuringResultList

Renders search results using the slot/render-prop pattern.

<TuringResultList
documents={documents}
isLoading={status === "loading"}
itemComponent={({ document, index }) => (
<div key={document.url}>
<h3>{document.title}</h3>
<p>{document.description}</p>
</div>
)}
emptyComponent={() => <p>No results found.</p>}
loadingComponent={() => <p>Loading...</p>}
/>
PropTypeDescription
documentsResolvedDocument[]Documents to render
itemComponent(props) => ReactNodeItem renderer (required)
emptyComponent() => ReactNodeNo-results state
loadingComponent() => ReactNodeLoading state
isLoadingbooleanShow loading component

TuringPagination

Renders pagination items with navigation actions.

<TuringPagination
items={data.pagination}
onNavigate={(href) => navigate(href)}
itemComponent={({ item, isCurrent, isEllipsis, onClick, label }) => (
<button
onClick={onClick}
disabled={isCurrent}
style={{ fontWeight: isCurrent ? "bold" : "normal" }}
>
{isEllipsis ? "..." : label}
</button>
)}
/>

Data Types

ResolvedDocument

Documents returned by hooks have default fields already mapped:

{
url: string; // Document URL
title: string; // Document title
description: string; // Document description
date: string; // Document date
image: string; // Image URL
text: string; // Full text
raw: TurDocument; // Full document with all custom fields
}

SearchParams

{
q: string; // Query string
p?: string; // Page number
_setlocale?: string; // Locale
sort?: string; // Sort field
fq?: string[]; // Facet queries (filters)
group?: string; // Group field name
rows?: string; // Results per page
}

Full Example: Search Page with Facets

import {
TuringProvider,
useTuringUrlSearch,
useTuringFacets,
useTuringPagination,
TuringResultList,
TuringSearchBar,
TuringPagination,
} from "@viglet/turing-react-sdk";
import { useSearchParams } from "react-router-dom";

function App() {
const [searchParams, setSearchParams] = useSearchParams();
return (
<TuringProvider
config={{ site: "my-site", locale: "en_US" }}
urlSync={{ searchParams, setSearchParams }}
>
<SearchPage />
</TuringProvider>
);
}

function SearchPage() {
const store = useTuringUrlSearch();
const { facetGroups, clearAll } = useTuringFacets();
const { pages } = useTuringPagination();

return (
<div>
<TuringSearchBar onSearch={(q) => store.searchQuery(q)} />

<aside>
{facetGroups.map((group) => (
<div key={group.name}>
<h4>{group.label}</h4>
{group.facets.map((facet) => (
<label key={facet.label}>
<input
type="checkbox"
checked={facet.selected}
onChange={facet.toggle}
/>
{facet.label} ({facet.count})
</label>
))}
</div>
))}
</aside>

<main>
<TuringResultList
documents={store.documents}
isLoading={store.status === "loading"}
itemComponent={({ document }) => (
<article>
<h3><a href={document.url}>{document.title}</a></h3>
<p>{document.description}</p>
</article>
)}
emptyComponent={() => <p>No results found.</p>}
/>

<TuringPagination
items={store.data?.pagination ?? []}
onNavigate={store.navigate}
/>
</main>
</div>
);
}