Skip to content

Commit 228c231

Browse files
Sembaukehuyenltnguyenojeytonwilliams
authored
chore: update react-instancesearch to v7 (freeCodeCamp#57020)
Co-authored-by: Huyen Nguyen <[email protected]> Co-authored-by: Oliver Eyton-Williams <[email protected]>
1 parent 6c34c2b commit 228c231

File tree

13 files changed

+872
-361
lines changed

13 files changed

+872
-361
lines changed

client/i18n/locales/english/translations.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@
160160
],
161161
"cta": "Start Learning Now (it's free)"
162162
},
163-
164163
"certification-heading": "Earn free verified certifications in:",
165164
"core-certs-heading": "Earn free verified certifications with freeCodeCamp's core curriculum:",
166165
"learn-english-heading": "Learn English for Developers:",
@@ -781,6 +780,7 @@
781780
"heart": "Heart",
782781
"initial": "Initial",
783782
"input-reset": "Clear search terms",
783+
"input-search": "Submit search terms",
784784
"info": "Intro Information",
785785
"spacer": "Spacer",
786786
"toggle": "Toggle Checkmark",

client/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"micromark": "4.0.0",
8585
"monaco-editor": "0.28.1",
8686
"nanoid": "3.3.7",
87+
"instantsearch.js": "4.75.3",
8788
"normalize-url": "4.5.1",
8889
"path-browserify": "1.0.1",
8990
"postcss": "8.4.35",
@@ -99,8 +100,8 @@
99100
"react-helmet": "6.1.0",
100101
"react-hotkeys": "2.0.0",
101102
"react-i18next": "12.3.1",
102-
"react-instantsearch-core": "6.40.4",
103-
"react-instantsearch-dom": "6.40.4",
103+
"react-instantsearch-core": "7.13.6",
104+
"react-instantsearch": "7.13.6",
104105
"react-monaco-editor": "0.40.0",
105106
"react-redux": "7.2.9",
106107
"react-reflex": "4.1.0",
@@ -143,8 +144,6 @@
143144
"@types/react-dom": "16.9.24",
144145
"@types/react-gtm-module": "2.0.3",
145146
"@types/react-helmet": "6.1.11",
146-
"@types/react-instantsearch-core": "6.26.10",
147-
"@types/react-instantsearch-dom": "6.12.8",
148147
"@types/react-redux": "7.1.33",
149148
"@types/react-responsive": "8.0.8",
150149
"@types/react-scrollable-anchor": "0.6.4",

client/src/components/search/searchBar/search-bar.tsx

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import React, { Component } from 'react';
33
import { HotKeys, ObserveKeys } from 'react-hotkeys';
44
import type { TFunction } from 'i18next';
55
import { withTranslation } from 'react-i18next';
6-
import { Hit } from 'react-instantsearch-core';
7-
import { SearchBox } from 'react-instantsearch-dom';
6+
import { SearchBox } from 'react-instantsearch';
87
import { connect } from 'react-redux';
98
import { AnyAction, bindActionCreators, Dispatch } from 'redux';
109
import { createSelector } from 'reselect';
@@ -14,15 +13,14 @@ import {
1413
isSearchDropdownEnabledSelector,
1514
isSearchBarFocusedSelector,
1615
toggleSearchDropdown,
17-
toggleSearchFocused,
18-
updateSearchQuery
16+
toggleSearchFocused
1917
} from '../redux';
2018
import WithInstantSearch from '../with-instant-search';
21-
22-
import SearchHits from './search-hits';
19+
import type { Hit } from './types';
2320

2421
import './searchbar-base.css';
2522
import './searchbar.css';
23+
import SearchHits from './search-hits';
2624

2725
const searchUrl = searchPageUrl;
2826
const mapStateToProps = createSelector(
@@ -35,16 +33,12 @@ const mapStateToProps = createSelector(
3533
);
3634

3735
const mapDispatchToProps = (dispatch: Dispatch<AnyAction>) =>
38-
bindActionCreators(
39-
{ toggleSearchDropdown, toggleSearchFocused, updateSearchQuery },
40-
dispatch
41-
);
36+
bindActionCreators({ toggleSearchDropdown, toggleSearchFocused }, dispatch);
4237

4338
export type SearchBarProps = {
4439
innerRef?: React.RefObject<HTMLDivElement>;
4540
toggleSearchDropdown: typeof toggleSearchDropdown;
4641
toggleSearchFocused: typeof toggleSearchFocused;
47-
updateSearchQuery: typeof updateSearchQuery;
4842
isDropdownEnabled?: boolean;
4943
isSearchFocused?: boolean;
5044
t: TFunction;
@@ -115,7 +109,7 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
115109
query?: string
116110
): boolean | void => {
117111
e.preventDefault();
118-
const { toggleSearchDropdown, updateSearchQuery } = this.props;
112+
const { toggleSearchDropdown } = this.props;
119113
const { index, hits } = this.state;
120114
const selectedHit = hits[index];
121115

@@ -128,7 +122,6 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
128122
// Set query to value in search bar if enter is pressed
129123
query = (e.currentTarget?.children?.[0] as HTMLInputElement).value;
130124
}
131-
updateSearchQuery(query);
132125

133126
//clear input value
134127
const searchInput = e.currentTarget?.children?.[0] as HTMLInputElement;
@@ -151,13 +144,18 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
151144

152145
handleMouseEnter = (e: React.SyntheticEvent<HTMLElement, Event>): void => {
153146
e.persist();
154-
const hoveredText = e.currentTarget.innerText;
155147

156148
this.setState(({ hits }) => {
157149
const hitsTitles = hits.map(hit => hit.title);
158-
const hoveredIndex = hitsTitles.indexOf(hoveredText);
159150

160-
return { index: hoveredIndex };
151+
if (e.target instanceof HTMLElement) {
152+
const targetText = e.target.textContent;
153+
const hoveredIndex = targetText ? hitsTitles.indexOf(targetText) : -1;
154+
155+
return { index: hoveredIndex };
156+
}
157+
158+
return { index: -1 };
161159
});
162160
};
163161

@@ -220,25 +218,23 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
220218
<ObserveKeys except={['Space']}>
221219
<SearchBox
222220
data-playwright-test-label='header-search'
223-
focusShortcuts={['83', '191']}
224-
onChange={this.handleChange}
225221
onSubmit={e => {
226222
this.handleSearch(e);
227223
}}
228-
showLoadingIndicator={false}
224+
onInput={this.handleChange}
229225
translations={{
230-
submitTitle: t('icons.magnifier'),
231-
resetTitle: t('icons.input-reset'),
232-
placeholder: searchPlaceholder
226+
submitButtonTitle: t('icons.input-search'),
227+
resetButtonTitle: t('icons.input-reset')
233228
}}
229+
placeholder={searchPlaceholder}
234230
onFocus={this.handleFocus}
235231
/>
236232
</ObserveKeys>
237233
{isDropdownEnabled && isSearchFocused && (
238234
<SearchHits
239-
handleHits={this.handleHits}
240235
handleMouseEnter={this.handleMouseEnter}
241236
handleMouseLeave={this.handleMouseLeave}
237+
handleHits={this.handleHits}
242238
selectedIndex={index}
243239
/>
244240
)}
Lines changed: 69 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,87 @@
11
import { isEmpty } from 'lodash-es';
22
import React, { useEffect } from 'react';
33
import { useTranslation } from 'react-i18next';
4-
import { SearchState, Hit } from 'react-instantsearch-core';
5-
import { connectStateResults, connectHits } from 'react-instantsearch-dom';
4+
import { useHits } from 'react-instantsearch';
65
import { searchPageUrl } from '../../../utils/algolia-locale-setup';
7-
import NoHitsSuggestion from './no-hits-suggestion';
86
import Suggestion from './search-suggestion';
7+
import NoHitsSuggestion from './no-hits-suggestion';
8+
import type { Hit } from './types';
99

1010
const searchUrl = searchPageUrl;
11-
interface CustomHitsProps {
12-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13-
hits: Array<any>;
14-
searchQuery: string;
15-
handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
16-
handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
17-
selectedIndex: number;
18-
handleHits: (currHits: Array<Hit>) => void;
19-
}
11+
2012
interface SearchHitsProps {
21-
searchState: SearchState;
2213
handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
2314
handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
15+
handleHits: (hits: Hit[]) => void;
2416
selectedIndex: number;
25-
handleHits: (currHits: Array<Hit>) => void;
2617
}
27-
const CustomHits = connectHits(
28-
({
29-
hits,
30-
searchQuery,
31-
handleMouseEnter,
32-
handleMouseLeave,
33-
selectedIndex,
34-
handleHits
35-
}: CustomHitsProps) => {
36-
const { t } = useTranslation();
37-
const noHits = isEmpty(hits);
38-
const noHitsTitle = t('search.no-tutorials');
39-
const footer = [
40-
{
41-
objectID: `footer-${searchQuery}`,
42-
query: searchQuery,
43-
url: noHits
44-
? null
45-
: `${searchUrl}?query=${encodeURIComponent(searchQuery)}`,
46-
title: t('search.see-results', { searchQuery: searchQuery }),
47-
_highlightResult: {
48-
query: {
49-
value: `
50-
<ais-highlight-0000000000>
51-
${t('search.see-results', { searchQuery: searchQuery })}
52-
</ais-highlight-0000000000>
53-
`
54-
}
18+
19+
function SearchHits({
20+
handleMouseEnter,
21+
handleMouseLeave,
22+
handleHits,
23+
selectedIndex
24+
}: SearchHitsProps) {
25+
const { results } = useHits<Hit>();
26+
const query = results ? results.query : '';
27+
const { t } = useTranslation();
28+
29+
const noHits = isEmpty(results?.hits);
30+
const noHitsTitle = t('search.no-tutorials');
31+
32+
const footer = [
33+
{
34+
__position: 8,
35+
objectID: `footer-${query}`,
36+
query: query,
37+
url: noHits ? '' : `${searchUrl}?query=${encodeURIComponent(query)}`,
38+
_highlightResult: {
39+
query: {
40+
value: `${t('search.see-results', { searchQuery: query })}`,
41+
matchLevel: 'none' as const,
42+
matchedWords: []
5543
}
5644
}
57-
];
58-
const allHits = hits.slice(0, 8).concat(footer);
59-
useEffect(() => {
60-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
61-
handleHits(allHits);
62-
});
45+
}
46+
];
47+
const allHits: Hit[] =
48+
results?.hits && results?.query ? [...results.hits, ...footer] : [];
6349

64-
return (
65-
<div className='ais-Hits'>
66-
<ul className='ais-Hits-list' aria-label={t('search.result-list')}>
67-
{allHits.map((hit: Hit, i: number) => (
68-
<li
69-
className={
70-
!noHits && i === selectedIndex
71-
? 'ais-Hits-item selected'
72-
: 'ais-Hits-item'
73-
}
74-
data-fccobjectid={hit.objectID}
75-
key={hit.objectID}
76-
>
77-
{noHits ? (
78-
<NoHitsSuggestion
79-
handleMouseEnter={handleMouseEnter}
80-
handleMouseLeave={handleMouseLeave}
81-
title={noHitsTitle}
82-
/>
83-
) : (
84-
<Suggestion
85-
handleMouseEnter={handleMouseEnter}
86-
handleMouseLeave={handleMouseLeave}
87-
hit={hit}
88-
/>
89-
)}
90-
</li>
91-
))}
92-
</ul>
93-
</div>
94-
);
95-
}
96-
);
50+
useEffect(() => {
51+
handleHits(allHits);
52+
});
9753

98-
const SearchHits = connectStateResults(
99-
({
100-
searchState,
101-
handleMouseEnter,
102-
handleMouseLeave,
103-
selectedIndex,
104-
handleHits
105-
}: SearchHitsProps) => {
106-
return isEmpty(searchState) || !searchState.query ? null : (
107-
<CustomHits
108-
handleHits={handleHits}
109-
handleMouseEnter={handleMouseEnter}
110-
handleMouseLeave={handleMouseLeave}
111-
searchQuery={searchState.query}
112-
selectedIndex={selectedIndex}
113-
/>
114-
);
115-
}
116-
);
54+
return (
55+
<div className='ais-Hits'>
56+
<ul className='ais-Hits-list' aria-label={t('search.result-list')}>
57+
{allHits.map((hit: Hit, i: number) => (
58+
<li
59+
className={
60+
!noHits && i === selectedIndex
61+
? 'ais-Hits-item selected'
62+
: 'ais-Hits-item'
63+
}
64+
data-fccobjectid={hit.objectID}
65+
key={hit.objectID}
66+
>
67+
{noHits ? (
68+
<NoHitsSuggestion
69+
handleMouseEnter={handleMouseEnter}
70+
handleMouseLeave={handleMouseLeave}
71+
title={noHitsTitle}
72+
/>
73+
) : (
74+
<Suggestion
75+
handleMouseEnter={handleMouseEnter}
76+
handleMouseLeave={handleMouseLeave}
77+
hit={hit}
78+
/>
79+
)}
80+
</li>
81+
))}
82+
</ul>
83+
</div>
84+
);
85+
}
11786

11887
export default SearchHits;

client/src/components/search/searchBar/search-suggestion.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
2-
import { Hit } from 'react-instantsearch-core';
3-
import { Highlight } from 'react-instantsearch-dom';
2+
import { Highlight } from 'react-instantsearch';
3+
import type { Hit } from './types';
44

55
interface SuggestionProps {
66
hit: Hit;
@@ -29,7 +29,7 @@ const Suggestion = ({
2929
>
3030
<span className='hit-name'>
3131
{dropdownFooter ? (
32-
<Highlight attribute='query' hit={hit} tagName='strong' />
32+
<Highlight attribute='query' hit={hit} />
3333
) : (
3434
<Highlight attribute='title' hit={hit} />
3535
)}

client/src/components/search/searchBar/searchbar.css

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
.fcc_searchBar .ais-Highlight-highlighted {
8989
background-color: transparent;
9090
font-style: normal;
91+
color: white;
9192
font-weight: bold;
9293
}
9394

@@ -134,6 +135,10 @@
134135
background-color: var(--gray-75);
135136
}
136137

138+
.ais-Hits-item:hover {
139+
background-color: var(--blue-dark);
140+
}
141+
137142
/* Hit selected with arrow keys or mouse */
138143
.selected {
139144
background-color: var(--blue-dark);
@@ -152,7 +157,7 @@ and arrow keys */
152157
padding: 6.5px 8px 8px;
153158
}
154159

155-
.fcc_suggestion_footer .hit-name .ais-Highlight {
160+
.fcc_suggestion_footer span[class='ais-Highlight-nonHighlighted'] {
156161
font-weight: bold;
157162
}
158163

0 commit comments

Comments
 (0)