Problem:
Connector HOCs add boilerplate and can be unwieldy. When you have an existing component already the modification to make it connected is a hassle. It also interferes with Flow type-checks because it adds "invisible" props and is hard to compose when you need multiple things.
For example:
```.jsx
// Base component
const Title = () => {
return
// With results
import { connectStateResults } from "react-instantsearch/connectors";
const Title = ({ searchResults }) => {
return
Redacted from these examples is Flow type checking and our styles HOC that makes it even more messy.
**Solution:**
React hooks offer a more encapsulated and concise way to do this:
```.jsx
// With results from hook
import { useStateResults } from "react-instantsearch/hooks";
const Title = () => {
const searchResults = useStateResults();
return <h1>{searchResults.hits.length} search results</h1>;
};
export default Title;
It is also much easier to deal with composing multiple things like results _and_ searchState:
```.jsx
// With results and searchState from hook
import { useStateResults, useSearchState } from "react-instantsearch/hooks";
const Title = () => {
const searchResults = useStateResults();
const searchState = useSearchState();
return
// Or alternatively in one:
import { useSearch } from "react-instantsearch/hooks";
const Title = () => {
const { searchState, searchResults } = useSearch();
return
This seems like a small change but having the Algolia parts decoupled from the props and export makes a big difference in aggregate.
Hi @GriffinSauce, thank you, I appreciate you taking the time to give us feedback.
This is likely something we'll work on in the future but I can't give an ETA, but it's nice to hear from the community about it.
I'll close this as it's already tracked by our internal project tracking tools.
I'm rather disappointed that Algolia has completely abandoned keeping progress with React. Hooks came out a year ago and are resolve significant pain points. As a senior freelance engineer that has a lot of weight in the technology decisions of startups, I'm unlikely to recommend Algolia any further as a result of this.
We take this kind of feedback seriously Corey, so thanks for bringing it up again!
We have it in our plans to make a refresh with hooks included, but it’s not straightforward to decide which api is nice to use with hooks compared to what we have now.
StateResults is indeed a good one to start with exposing, but do you have an idea what you’d like to use?
We are all ears
@coreyward this is the hook I ended up building for us:
https://gist.github.com/GriffinSauce/0451d328511b4e82469f4e9f688e5118
You can dump the return into the context provider and use the extra hook to grab those again down the tree. That's a super easy way to connect stuff up, you could probably make one hook to both (initialise / grab whatever is already there) but I like the explicit nature of these.
This works for us, it might not fit your context but maybe it helps. Let me know if you end up using it and see any possible improvements. :)
@Haroenv it could help to start small, as you can see from ours we really just need results, loading state and changing the search.
@Haroenv My use case is relatively simple, effectively a straightforward instant search page with a search input and some little search result tiles. Single index, no facets, no pagination, etc. I ultimately did the same as @GriffinSauce and simply integrated directly using algoliasearch/lite.
Here's what my integration looks like (slightly simplified, but this is the bulk of the logic):
import React from "react"
import algoliaClient from "algoliasearch/lite"
import debounce from "lodash-es/debounce"
const algolia = algoliaClient(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_SEARCH_KEY
).initIndex("pages")
const search = (query, params = {}) =>
algolia.search(query, {
attributesToHighlight: null,
hitsPerPage: 60,
...params,
})
const Search = () => {
const [loading, setLoading] = useState(false)
const [results, setResults] = useState([])
const [query, setQuery] = useState("")
const updateResults = useCallback(
debounce(currentQuery => {
search(currentQuery).then(({ hits }) => {
setResults(hits)
setLoading(false)
})
}, 250),
[]
)
const handleChange = useCallback(
e => {
setQuery(e.target.value)
if (e.target.value.length > 0) {
setLoading(true)
updateResults(e.target.value)
} else {
setLoading(false)
}
},
[updateResults]
)
return (
<div>
<input type="search" onChange={handleChange} />
{results.length > 0 ? (
<Results results={results} />
) : (
query.length > 0 && !loading && (
<div>No results</div>
)
)}
</div>
)
}
As you can see, there's not much needed, but it would be much preferred if this type of approach worked instead, though:
import React from "react"
import { useSearch } from "react-algolia-search"
const Search = () => {
const { query, results, loading, setQuery } = useSearch(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_SEARCH_KEY
)
return (
<div>
<input type="search" onChange={e => { setQuery(e.target.value) }} />
{results.length > 0 ? (
<Results query={query} results={results} />
) : (
loading
? "Loading…"
: query.length > 0 && `No results for “${query}”.`
)}
</div>
)
}
Obviously you could add additional fields and params to this to accept additional params, tune the debouncing (the lack of any debouncing in the existing React InstantSearch is super wasteful), and so on.
I'd love to see this too, I've written a handful of components for React Native that use the connectors, but the job would be so much easier done with Hooks, also type safety would be easier to manage.
I'd love to see exposure through hooks for a combination of SearchBox, Autocomplete and InifiniteHits, like @coreyward describes.
e.g. const { hits, refine, refineNext, refinePrevious, query, hasMore, setOptions } = useInstantSearch()
Just chiming in too, I am one of the developers to blame here: I did not see hooks coming when I was working at Algolia mostly because I was not building enough real world React apps to see that hooks were everywhere in the ecosystem.
Now that I am heavy in building a real world React app and using many other libraries, I can see it clearly: hooks are everywhere and yes it would be awesome to be able to use hooks to build an Algolia experience.
Even if it starts very lightly at first like the proposal from @coreyward, it would be awesome!
I wrote my own hook for Algolia in my React Native/Expo app using Vercel's swr as the fetching mechanism under the hood.
import { SearchResponse } from '@algolia/client-search'
import { useState, useEffect } from 'react'
import useSWR, { ConfigInterface, responseInterface } from 'swr'
import { algolia } from '../server/algolia/algolia-client'
export const useSearchState = () => {
const [query, setQuery] = useState('')
return {
query,
setQuery,
}
}
type Options = {
facets?: string[]
index?: string
}
type Returns<T> = responseInterface<
SearchResponse<AlgoliaHit<T>> | undefined,
unknown
>
type AlgoliaHit<T> = T & { _highlightResult?: HighlightResult<T> }
type HighlightResult<T> = {
[key in keyof T]?: {
value?: string
matchLevel?: 'none' | 'full'
matchedWords?: string[]
fullyHighligted?: boolean
}
}
export function useAlgoliaSearch<T>(
query: string,
options: Options = {},
swr: ConfigInterface<SearchResponse<AlgoliaHit<T>>> = {}
): Returns<T> {
const { index, facets } = options
const makeSWRKey = ({
query,
facets,
index,
}: {
query: string
facets?: string[]
index?: string
}) => {
return ['algolia-search', query, index, JSON.stringify(facets)]
}
const response = useSWR(
() => makeSWRKey({ query, facets, index }),
async (
...[
_,
query = '',
index = 'artists', // Defaulted to my typical index
facetString,
]: ReturnType<typeof makeSWRKey>
) => {
const facets = facetString ? JSON.parse(facetString) ?? [] : undefined
const results = await algolia
.initIndex(index)
.search<AlgoliaHit<T>>(query, {
facets,
})
return results
},
{
refreshInterval: 0,
refreshWhenHidden: false,
...swr,
}
)
return response
}
And then in my screen:
import React from 'react'
import { useSearchState, useAlgoliaSearch } from './algolia-search'
type Artist = {
name: string
}
export default function Search() {
const { query, setQuery } = useSearchState()
const { data, error } = useAlgoliaSearch<Artist>(query, {
facets: ['genre', 'popularity'] // optional
})
if (error) return <Text>Error...</Text>
if (!data) return <Text>Loading...</Text>
return data.hits.map(({ name, objectID }) => <Text key={objectID}>{name}</Text>)
}
this is really interesting for non-faceting use cases, thanks @nandorojo
@Haroenv I did include facets too. You'll have to use your own react state management for picking them, though.
const { data, error } = useAlgoliaSearch(query, {
facets: ['genre', 'popularity']
})
I just updated my example with this ^
Update, I now see what you mean. I'll probably add in filters, too. I'm new to Algolia, so still figuring out the terminology and such.
Yes, what I meant is not that your implementation is lacking, but that it doesn't have individual pagination, refinement list etc. widgets / imports. That's totally expected of course from a just new project.
@Haroenv Makes sense. I'll probably use useSWRInfinite to handle infinite scrolling as my project expands. It wouldn't be too hard to add pagination to my current hook above, too.
@Haroenv any idea how I could implement a refinement list with hooks? I'm not sure how to even fetch the list of possible refinements via the JS SDK directly. Does it require that I use the context from the Algolia react provider?
We rely on the helper for doing requests which need multiple queries, e.g. for disjunctive facet count. You can look at the code for connectRefinementList on how we use it.
For those searching for a solution, I found: https://www.npmjs.com/package/use-algolia
I must say it's a little disappointing that this ticket was opened in 2019 and here we are in 2021 with no solution from Algolia yet. I can't think of any popular React based library in 2021 that doesn't have a hooks implementation, let alone a commercial product.
Would be nice to have a roadmap from the algolia team. Because from where I see, hooks should be in the higher priority position. Right now I'm in a situation where I need to connect the same component twice (connectStateResults, connectPagination) and I'm giving up implementing this, the only way out is repeating the code.
Most helpful comment
Just chiming in too, I am one of the developers to blame here: I did not see hooks coming when I was working at Algolia mostly because I was not building enough real world React apps to see that hooks were everywhere in the ecosystem.
Now that I am heavy in building a real world React app and using many other libraries, I can see it clearly: hooks are everywhere and yes it would be awesome to be able to use hooks to build an Algolia experience.
Even if it starts very lightly at first like the proposal from @coreyward, it would be awesome!