Inject Third-Party Content
Sometimes you may need to inject content that’s not in Algolia. This could be static data or even data coming from a third-party API.
In those cases, you can provide the data to inject using slotComponent
and omit getHits
.
Requirements
React InstantSearch exposes a connector API that lets you reuse existing logic and plug your own. With it, you can build a custom React InstantSearch widget to mix regular Algolia results with injected content. Make sure you’re familiar with this concept before continuing to the next section.
Inject static data
To inject static data, such as JSON from a file, or hard coded information, you can provide it as a component to slotComponent
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React from 'react';
import {
InstantSearch,
Configure,
Index,
SearchBox,
} from 'react-instantsearch-dom';
import { InjectedHits } from './InjectedHits';
function App() {
return (
<InstantSearch searchClient={searchClient} indexName="ingredients">
<Configure hitsPerPage={8} />
<SearchBox />
<InjectedHits
slots={() => [
{
injectAt: 3,
slotComponent: BannerHit,
},
]}
hitComponent={IngredientHit}
/>
</InstantSearch>
);
}
function BannerHit() {
return (
<a href="https://example.org/">
<img src="./path/to/image.jpg" alt="My banner" />
</a>
)
}
// ...
Inject remote data
If your data comes from a remote source, like a third-party API, you can fetch it from the component directly.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import React from 'react';
import {
InstantSearch,
Configure,
SearchBox,
} from 'react-instantsearch-dom';
import { InjectedHits } from './InjectedHits';
function App() {
return (
<InstantSearch searchClient={searchClient} indexName="ingredients">
<Configure hitsPerPage={8} />
<SearchBox />
<InjectedHits
slots={() => [
{
injectAt: 3,
slotComponent: BannerHit,
},
]}
hitComponent={IngredientHit}
/>
</InstantSearch>
);
}
function BannerHit({ resultsByIndex }) {
const [data, setData] = useState(null);
useEffect(() => {
if (resultsByIndex.products?.query) {
fetch(`https://example.org/api/q=${resultsByIndex.products?.query}`)
.then((r) => r.json())
.then(setData);
}
}, [resultsByIndex.products]);
if (!data) {
return <div>Loading...</div>;
}
return (
<a href={data.url}>
<img src={data.image} alt={data.name} />
</a>
);
}
// ...
The widget re-renders once the data comes in. This can result in layout shift, where the banner pushes hits that the user was looking at, or about to click.
To avoid it, make sure to always provide fallback content of the same size as the target one.
Avoid layout shift
Third-party content is fetched on render (or during render if you’re using Suspense), meaning your Algolia hits always come first. To avoid layout shift, make sure to always provide fallback content with the same size as the target one.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ...
function BannerHit({ resultsByIndex }) {
const [data, setData] = useState(null);
useEffect(() => {
if (resultsByIndex.products?.query) {
fetch(`https://example.org/api/q=${resultsByIndex.products?.query}`)
.then((r) => r.json())
.then(setData);
}
}, [resultsByIndex.products]);
if (!data) {
// Render a fallback right away at the right dimensions
// to avoid Cumulative Layout Shift.
return <div style={{ width: '100%', height: '200px' }}>Loading...</div>;
}
return (
<a href={data.url} style={{ width: '100%', height: '200px' }}>
<img src={data.image} alt={data.name} />
</a>
);
}
Handle network errors
Network requests can fail: the API can be down, the user can block requests to advertising services with a browser extension, the network can be slow or unstable. Not being able to render injected hits shouldn’t have an impact on the search experience. Errors should be handled locally, in their component.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ...
function BannerHit({ resultsByIndex }) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
if (resultsByIndex.products?.query) {
fetch(`https://example.org/api/q=${resultsByIndex.products?.query}`)
.then((r) => r.json())
.then((data) => {
setData(data);
setError(null);
})
.catch((err) => {
setError(err);
setData(null);
});
}
}, [resultsByIndex.products]);
if (error) {
return null;
}
// ...
}
To avoid layout shift when you get an error, you can provide alternative content instead of removing the block. For example, you could display static data or data coming from Algolia.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ...
function BannerHit({ hit }) {
// ...
if (error) {
return (
<div style={{ width: '100%', height: '200px' }}>
{/* Some error fallback content */}
</div>
);
}
return (
<a href={data.url} style={{ width: '100%', height: '200px' }}>
<img src={data.image} alt={data.name} />
</a>
);
}