From 876638194c816a3b83ab2acff41dd26ea0584c98 Mon Sep 17 00:00:00 2001 From: Adarsh Bhadauria Date: Mon, 9 Jan 2023 14:21:40 +0530 Subject: [PATCH] Fix search bar disappears on mobile (#18909) --- app/javascript/Search/SearchFormSync.jsx | 24 ++++++++++++++----- .../Search/__tests__/SearchFormSync.test.jsx | 22 +++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/app/javascript/Search/SearchFormSync.jsx b/app/javascript/Search/SearchFormSync.jsx index 1e95d0fd5..664ad174c 100644 --- a/app/javascript/Search/SearchFormSync.jsx +++ b/app/javascript/Search/SearchFormSync.jsx @@ -25,7 +25,7 @@ export function SearchFormSync() { // 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'); + const mscElement = 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. @@ -33,17 +33,29 @@ export function SearchFormSync() { // 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) { + if (mobileSearchContainer && mscElement !== 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); + if (mscElement) { + const form = mscElement.querySelector('form'); + // We remove the child form expecting that createPortal adds the new form on each re-render + form && mscElement.removeChild(form); } - setMobileSearchContainer(element); + // Since, passing the reference to same element will not cause the component to re-render + // we CLONE & REPLACE the element with the clone & pass the clone's reference, so that useState + // will re-render the component. + const mscElementClone = mscElement ? mscElement.cloneNode(true) : null; + + if (mscElementClone) { + const mscElementParent = mscElement.parentElement; + unmountComponentAtNode(mscElement); + mscElementParent.prepend(mscElementClone); + } + + setMobileSearchContainer(mscElementClone); setSearchTerm(updatedSearchTerm); } diff --git a/app/javascript/Search/__tests__/SearchFormSync.test.jsx b/app/javascript/Search/__tests__/SearchFormSync.test.jsx index 344541b51..2309e8066 100644 --- a/app/javascript/Search/__tests__/SearchFormSync.test.jsx +++ b/app/javascript/Search/__tests__/SearchFormSync.test.jsx @@ -118,4 +118,26 @@ describe('', () => { expect(desktopSearch.value).toEqual(searchTerm2); expect(mobileSearch.value).toEqual(searchTerm2); }); + + it('ensure both mobile search form & desktop search form exists', async () => { + const { getAllByRole } = render(); + + setWindowLocation(`https://localhost:3000/search`); + + // fire click event twice with empty query to mimic clicking search button twice present in header + for (let i = 0; i < 2; i += 1) { + fireEvent( + window, + new CustomEvent('syncSearchForms', { + detail: { querystring: window.location.search }, + }), + ); + } + + const forms = await getAllByRole('textbox', { + name: /search/i, + }); + + expect(forms.length).toEqual(2); + }); });