Skip to content

Commit

Permalink
chore: update react-instancesearch to v7 (freeCodeCamp#57020)
Browse files Browse the repository at this point in the history
Co-authored-by: Huyen Nguyen <[email protected]>
Co-authored-by: Oliver Eyton-Williams <[email protected]>
  • Loading branch information
3 people authored Nov 8, 2024
1 parent 6c34c2b commit 228c231
Show file tree
Hide file tree
Showing 13 changed files with 872 additions and 361 deletions.
2 changes: 1 addition & 1 deletion client/i18n/locales/english/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@
],
"cta": "Start Learning Now (it's free)"
},

"certification-heading": "Earn free verified certifications in:",
"core-certs-heading": "Earn free verified certifications with freeCodeCamp's core curriculum:",
"learn-english-heading": "Learn English for Developers:",
Expand Down Expand Up @@ -781,6 +780,7 @@
"heart": "Heart",
"initial": "Initial",
"input-reset": "Clear search terms",
"input-search": "Submit search terms",
"info": "Intro Information",
"spacer": "Spacer",
"toggle": "Toggle Checkmark",
Expand Down
7 changes: 3 additions & 4 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"micromark": "4.0.0",
"monaco-editor": "0.28.1",
"nanoid": "3.3.7",
"instantsearch.js": "4.75.3",
"normalize-url": "4.5.1",
"path-browserify": "1.0.1",
"postcss": "8.4.35",
Expand All @@ -99,8 +100,8 @@
"react-helmet": "6.1.0",
"react-hotkeys": "2.0.0",
"react-i18next": "12.3.1",
"react-instantsearch-core": "6.40.4",
"react-instantsearch-dom": "6.40.4",
"react-instantsearch-core": "7.13.6",
"react-instantsearch": "7.13.6",
"react-monaco-editor": "0.40.0",
"react-redux": "7.2.9",
"react-reflex": "4.1.0",
Expand Down Expand Up @@ -143,8 +144,6 @@
"@types/react-dom": "16.9.24",
"@types/react-gtm-module": "2.0.3",
"@types/react-helmet": "6.1.11",
"@types/react-instantsearch-core": "6.26.10",
"@types/react-instantsearch-dom": "6.12.8",
"@types/react-redux": "7.1.33",
"@types/react-responsive": "8.0.8",
"@types/react-scrollable-anchor": "0.6.4",
Expand Down
42 changes: 19 additions & 23 deletions client/src/components/search/searchBar/search-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import React, { Component } from 'react';
import { HotKeys, ObserveKeys } from 'react-hotkeys';
import type { TFunction } from 'i18next';
import { withTranslation } from 'react-i18next';
import { Hit } from 'react-instantsearch-core';
import { SearchBox } from 'react-instantsearch-dom';
import { SearchBox } from 'react-instantsearch';
import { connect } from 'react-redux';
import { AnyAction, bindActionCreators, Dispatch } from 'redux';
import { createSelector } from 'reselect';
Expand All @@ -14,15 +13,14 @@ import {
isSearchDropdownEnabledSelector,
isSearchBarFocusedSelector,
toggleSearchDropdown,
toggleSearchFocused,
updateSearchQuery
toggleSearchFocused
} from '../redux';
import WithInstantSearch from '../with-instant-search';

import SearchHits from './search-hits';
import type { Hit } from './types';

import './searchbar-base.css';
import './searchbar.css';
import SearchHits from './search-hits';

const searchUrl = searchPageUrl;
const mapStateToProps = createSelector(
Expand All @@ -35,16 +33,12 @@ const mapStateToProps = createSelector(
);

const mapDispatchToProps = (dispatch: Dispatch<AnyAction>) =>
bindActionCreators(
{ toggleSearchDropdown, toggleSearchFocused, updateSearchQuery },
dispatch
);
bindActionCreators({ toggleSearchDropdown, toggleSearchFocused }, dispatch);

export type SearchBarProps = {
innerRef?: React.RefObject<HTMLDivElement>;
toggleSearchDropdown: typeof toggleSearchDropdown;
toggleSearchFocused: typeof toggleSearchFocused;
updateSearchQuery: typeof updateSearchQuery;
isDropdownEnabled?: boolean;
isSearchFocused?: boolean;
t: TFunction;
Expand Down Expand Up @@ -115,7 +109,7 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
query?: string
): boolean | void => {
e.preventDefault();
const { toggleSearchDropdown, updateSearchQuery } = this.props;
const { toggleSearchDropdown } = this.props;
const { index, hits } = this.state;
const selectedHit = hits[index];

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

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

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

this.setState(({ hits }) => {
const hitsTitles = hits.map(hit => hit.title);
const hoveredIndex = hitsTitles.indexOf(hoveredText);

return { index: hoveredIndex };
if (e.target instanceof HTMLElement) {
const targetText = e.target.textContent;
const hoveredIndex = targetText ? hitsTitles.indexOf(targetText) : -1;

return { index: hoveredIndex };
}

return { index: -1 };
});
};

Expand Down Expand Up @@ -220,25 +218,23 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
<ObserveKeys except={['Space']}>
<SearchBox
data-playwright-test-label='header-search'
focusShortcuts={['83', '191']}
onChange={this.handleChange}
onSubmit={e => {
this.handleSearch(e);
}}
showLoadingIndicator={false}
onInput={this.handleChange}
translations={{
submitTitle: t('icons.magnifier'),
resetTitle: t('icons.input-reset'),
placeholder: searchPlaceholder
submitButtonTitle: t('icons.input-search'),
resetButtonTitle: t('icons.input-reset')
}}
placeholder={searchPlaceholder}
onFocus={this.handleFocus}
/>
</ObserveKeys>
{isDropdownEnabled && isSearchFocused && (
<SearchHits
handleHits={this.handleHits}
handleMouseEnter={this.handleMouseEnter}
handleMouseLeave={this.handleMouseLeave}
handleHits={this.handleHits}
selectedIndex={index}
/>
)}
Expand Down
169 changes: 69 additions & 100 deletions client/src/components/search/searchBar/search-hits.tsx
Original file line number Diff line number Diff line change
@@ -1,118 +1,87 @@
import { isEmpty } from 'lodash-es';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { SearchState, Hit } from 'react-instantsearch-core';
import { connectStateResults, connectHits } from 'react-instantsearch-dom';
import { useHits } from 'react-instantsearch';
import { searchPageUrl } from '../../../utils/algolia-locale-setup';
import NoHitsSuggestion from './no-hits-suggestion';
import Suggestion from './search-suggestion';
import NoHitsSuggestion from './no-hits-suggestion';
import type { Hit } from './types';

const searchUrl = searchPageUrl;
interface CustomHitsProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
hits: Array<any>;
searchQuery: string;
handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
selectedIndex: number;
handleHits: (currHits: Array<Hit>) => void;
}

interface SearchHitsProps {
searchState: SearchState;
handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
handleHits: (hits: Hit[]) => void;
selectedIndex: number;
handleHits: (currHits: Array<Hit>) => void;
}
const CustomHits = connectHits(
({
hits,
searchQuery,
handleMouseEnter,
handleMouseLeave,
selectedIndex,
handleHits
}: CustomHitsProps) => {
const { t } = useTranslation();
const noHits = isEmpty(hits);
const noHitsTitle = t('search.no-tutorials');
const footer = [
{
objectID: `footer-${searchQuery}`,
query: searchQuery,
url: noHits
? null
: `${searchUrl}?query=${encodeURIComponent(searchQuery)}`,
title: t('search.see-results', { searchQuery: searchQuery }),
_highlightResult: {
query: {
value: `
<ais-highlight-0000000000>
${t('search.see-results', { searchQuery: searchQuery })}
</ais-highlight-0000000000>
`
}

function SearchHits({
handleMouseEnter,
handleMouseLeave,
handleHits,
selectedIndex
}: SearchHitsProps) {
const { results } = useHits<Hit>();
const query = results ? results.query : '';
const { t } = useTranslation();

const noHits = isEmpty(results?.hits);
const noHitsTitle = t('search.no-tutorials');

const footer = [
{
__position: 8,
objectID: `footer-${query}`,
query: query,
url: noHits ? '' : `${searchUrl}?query=${encodeURIComponent(query)}`,
_highlightResult: {
query: {
value: `${t('search.see-results', { searchQuery: query })}`,
matchLevel: 'none' as const,
matchedWords: []
}
}
];
const allHits = hits.slice(0, 8).concat(footer);
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
handleHits(allHits);
});
}
];
const allHits: Hit[] =
results?.hits && results?.query ? [...results.hits, ...footer] : [];

return (
<div className='ais-Hits'>
<ul className='ais-Hits-list' aria-label={t('search.result-list')}>
{allHits.map((hit: Hit, i: number) => (
<li
className={
!noHits && i === selectedIndex
? 'ais-Hits-item selected'
: 'ais-Hits-item'
}
data-fccobjectid={hit.objectID}
key={hit.objectID}
>
{noHits ? (
<NoHitsSuggestion
handleMouseEnter={handleMouseEnter}
handleMouseLeave={handleMouseLeave}
title={noHitsTitle}
/>
) : (
<Suggestion
handleMouseEnter={handleMouseEnter}
handleMouseLeave={handleMouseLeave}
hit={hit}
/>
)}
</li>
))}
</ul>
</div>
);
}
);
useEffect(() => {
handleHits(allHits);
});

const SearchHits = connectStateResults(
({
searchState,
handleMouseEnter,
handleMouseLeave,
selectedIndex,
handleHits
}: SearchHitsProps) => {
return isEmpty(searchState) || !searchState.query ? null : (
<CustomHits
handleHits={handleHits}
handleMouseEnter={handleMouseEnter}
handleMouseLeave={handleMouseLeave}
searchQuery={searchState.query}
selectedIndex={selectedIndex}
/>
);
}
);
return (
<div className='ais-Hits'>
<ul className='ais-Hits-list' aria-label={t('search.result-list')}>
{allHits.map((hit: Hit, i: number) => (
<li
className={
!noHits && i === selectedIndex
? 'ais-Hits-item selected'
: 'ais-Hits-item'
}
data-fccobjectid={hit.objectID}
key={hit.objectID}
>
{noHits ? (
<NoHitsSuggestion
handleMouseEnter={handleMouseEnter}
handleMouseLeave={handleMouseLeave}
title={noHitsTitle}
/>
) : (
<Suggestion
handleMouseEnter={handleMouseEnter}
handleMouseLeave={handleMouseLeave}
hit={hit}
/>
)}
</li>
))}
</ul>
</div>
);
}

export default SearchHits;
6 changes: 3 additions & 3 deletions client/src/components/search/searchBar/search-suggestion.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Hit } from 'react-instantsearch-core';
import { Highlight } from 'react-instantsearch-dom';
import { Highlight } from 'react-instantsearch';
import type { Hit } from './types';

interface SuggestionProps {
hit: Hit;
Expand Down Expand Up @@ -29,7 +29,7 @@ const Suggestion = ({
>
<span className='hit-name'>
{dropdownFooter ? (
<Highlight attribute='query' hit={hit} tagName='strong' />
<Highlight attribute='query' hit={hit} />
) : (
<Highlight attribute='title' hit={hit} />
)}
Expand Down
7 changes: 6 additions & 1 deletion client/src/components/search/searchBar/searchbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
.fcc_searchBar .ais-Highlight-highlighted {
background-color: transparent;
font-style: normal;
color: white;
font-weight: bold;
}

Expand Down Expand Up @@ -134,6 +135,10 @@
background-color: var(--gray-75);
}

.ais-Hits-item:hover {
background-color: var(--blue-dark);
}

/* Hit selected with arrow keys or mouse */
.selected {
background-color: var(--blue-dark);
Expand All @@ -152,7 +157,7 @@ and arrow keys */
padding: 6.5px 8px 8px;
}

.fcc_suggestion_footer .hit-name .ais-Highlight {
.fcc_suggestion_footer span[class='ais-Highlight-nonHighlighted'] {
font-weight: bold;
}

Expand Down
Loading

0 comments on commit 228c231

Please sign in to comment.