* Prep work to sync mobile search in #10424 * Added a comment about the custom search event. * Refactor to fix already defined error caused by const and InstantClick. * Moved <SearchFormSync /> out of the pack file. * Fixed broken tests. * Renamed getInitialSearchTerm utility function to getSearchTermFromUrl * Added some API docs to getSearchTermFromUrl * Fixed a typo. * Added some more API docs. * Switched to a custom event so a custom payload can be passed. * Added some API docs. * Fixed bad destructuring statement. * wip for some more tests. * Refactored getSearchTermFromUrl to use URLSearchParams * lazy loaded state for search term initial value. * test broken still. Need to think on it. * filterXSS is now set up in testSetup.js * Fixed tests. * Added the @utilities alias to Storybooks webpack config. * Almost 100% coverage with two useful tests. * Added some comments explaining document.body usage in the tests. * Fixed issue where search attemps to get results on first load. * Small refactor for setting the window.location in tests. * Clarified an explanation in a comment.
68 lines
2.6 KiB
JavaScript
68 lines
2.6 KiB
JavaScript
import { h } from 'preact';
|
|
import { useEffect, useState } from 'preact/hooks';
|
|
import { createPortal, Fragment, unmountComponentAtNode } from 'preact/compat';
|
|
import { Search } from './Search';
|
|
import { getSearchTermFromUrl } from '@utilities/search';
|
|
|
|
/**
|
|
* Manages the synchronization of search state between the top search bar (desktop) and
|
|
* mobile (in search results page).
|
|
*/
|
|
export function SearchFormSync() {
|
|
const [searchTerm, setSearchTerm] = useState(() => {
|
|
return getSearchTermFromUrl(location.search);
|
|
});
|
|
const [mobileSearchContainer, setMobileSearchContainer] = useState(null);
|
|
|
|
/**
|
|
* A listener for handling the synchronization of search forms.
|
|
*
|
|
* @param {CustomEvent<{ querystring: string }>} event A custom event for synching search forms.
|
|
*/
|
|
function syncSearchFormsListener(event) {
|
|
const { querystring } = event.detail;
|
|
const updatedSearchTerm = getSearchTermFromUrl(querystring);
|
|
|
|
// Server-side rendering of search results means the DOM node is destroyed everytime a search is performed,
|
|
// So we need to get the reference every time and use that for the parent in the portal.
|
|
const element = document.getElementById('mobile-search-container');
|
|
|
|
// The DOM element has changed because server-sde rendering returns new
|
|
// search results which destroys the existing search form in mobile view.
|
|
// Because of this we need to unmount the component at the old element reference
|
|
// i.e. the container for the createPortal call in the render.
|
|
// If we do not unmount, it will result in an unmounting error that will throw as the
|
|
// container element (search form that was wiped out because of the new search results) no longer exists.
|
|
if (mobileSearchContainer && element !== mobileSearchContainer) {
|
|
unmountComponentAtNode(mobileSearchContainer);
|
|
}
|
|
|
|
// We need to delete the existing server-side rendered form because createPortal only appends to it's container.
|
|
if (element) {
|
|
const form = element.querySelector('form');
|
|
form && element.removeChild(form);
|
|
}
|
|
|
|
setMobileSearchContainer(element);
|
|
setSearchTerm(updatedSearchTerm);
|
|
}
|
|
|
|
useEffect(() => {
|
|
window.addEventListener('syncSearchForms', syncSearchFormsListener);
|
|
|
|
return () => {
|
|
window.removeEventListener('syncSearchForms', syncSearchFormsListener);
|
|
};
|
|
});
|
|
|
|
return (
|
|
<Fragment>
|
|
<Search searchTerm={searchTerm} setSearchTerm={setSearchTerm} />
|
|
{mobileSearchContainer &&
|
|
createPortal(
|
|
<Search searchTerm={searchTerm} setSearchTerm={setSearchTerm} />,
|
|
mobileSearchContainer,
|
|
)}
|
|
</Fragment>
|
|
);
|
|
}
|