feat(client): use url query param on search page

This commit is contained in:
ValeraS
2019-06-10 22:53:15 +03:00
committed by Mrugesh Mohapatra
parent aee2f5e536
commit 84c46750e8
4 changed files with 76 additions and 20 deletions

View File

@@ -132,7 +132,6 @@ class DefaultLayout extends Component {
isSignedIn, isSignedIn,
landingPage, landingPage,
navigationMenu, navigationMenu,
pathname,
removeFlashMessage, removeFlashMessage,
showFooter = true showFooter = true
} = this.props; } = this.props;
@@ -151,7 +150,7 @@ class DefaultLayout extends Component {
> >
<style>{fontawesome.dom.css()}</style> <style>{fontawesome.dom.css()}</style>
</Helmet> </Helmet>
<WithInstantSearch pathname={pathname}> <WithInstantSearch>
<Header <Header
disableSettings={landingPage} disableSettings={landingPage}
navigationMenu={navigationMenu} navigationMenu={navigationMenu}

View File

@@ -1,7 +1,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Location } from '@reach/router';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { InstantSearch, Configure } from 'react-instantsearch-dom'; import { InstantSearch, Configure } from 'react-instantsearch-dom';
import qs from 'query-string';
import { import {
isSearchDropdownEnabledSelector, isSearchDropdownEnabledSelector,
@@ -15,7 +17,7 @@ import { createSelector } from 'reselect';
const propTypes = { const propTypes = {
children: PropTypes.any, children: PropTypes.any,
isDropdownEnabled: PropTypes.bool, isDropdownEnabled: PropTypes.bool,
pathname: PropTypes.string.isRequired, location: PropTypes.object.isRequired,
query: PropTypes.string, query: PropTypes.string,
toggleSearchDropdown: PropTypes.func.isRequired, toggleSearchDropdown: PropTypes.func.isRequired,
updateSearchQuery: PropTypes.func.isRequired updateSearchQuery: PropTypes.func.isRequired
@@ -31,33 +33,71 @@ const mapDispatchToProps = {
updateSearchQuery updateSearchQuery
}; };
class WithInstantSearch extends Component { const searchStateToUrl = ({ pathname }, query) =>
`${pathname}${query ? `?${qs.stringify({ query })}` : ''}`;
const urlToSearchState = ({ search }) => qs.parse(search.slice(1));
class InstantSearchRoot extends Component {
componentDidMount() { componentDidMount() {
const { toggleSearchDropdown } = this.props; const { toggleSearchDropdown } = this.props;
toggleSearchDropdown(this.getSearchEnableDropdown()); toggleSearchDropdown(!this.isSearchPage());
this.setQueryFromURL();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { pathname, toggleSearchDropdown, isDropdownEnabled } = this.props; const { location, toggleSearchDropdown, isDropdownEnabled } = this.props;
const { pathname: prevPathname } = prevProps;
const enableDropdown = this.getSearchEnableDropdown(); const enableDropdown = !this.isSearchPage();
if (pathname !== prevPathname || isDropdownEnabled !== enableDropdown) { if (isDropdownEnabled !== enableDropdown) {
toggleSearchDropdown(enableDropdown); toggleSearchDropdown(enableDropdown);
} }
const { query, updateSearchQuery } = this.props;
if (query && pathname !== prevPathname && enableDropdown) { if (location !== prevProps.location) {
updateSearchQuery(''); const { query, updateSearchQuery } = this.props;
if (this.isSearchPage()) {
this.setQueryFromURL();
} else if (query) {
updateSearchQuery('');
}
} }
} }
getSearchEnableDropdown = () => !this.props.pathname.startsWith('/search'); isSearchPage = () => this.props.location.pathname.startsWith('/search');
setQueryFromURL = () => {
if (this.isSearchPage()) {
const { updateSearchQuery, location, query } = this.props;
const { query: queryFromURL } = urlToSearchState(location);
if (query !== queryFromURL) {
updateSearchQuery(queryFromURL);
}
}
};
onSearchStateChange = ({ query }) => { onSearchStateChange = ({ query }) => {
const { updateSearchQuery, query: propsQuery } = this.props; const { updateSearchQuery, query: propsQuery } = this.props;
if (propsQuery === query || typeof query === 'undefined') { if (propsQuery === query || typeof query === 'undefined') {
return null; return;
}
updateSearchQuery(query);
this.updateBrowserHistory(query);
};
updateBrowserHistory = query => {
if (this.isSearchPage()) {
clearTimeout(this.debouncedSetState);
this.debouncedSetState = setTimeout(() => {
if (this.isSearchPage()) {
window.history.pushState(
{ query },
null,
searchStateToUrl(this.props.location, query)
);
}
}, 400);
} }
return updateSearchQuery(query);
}; };
render() { render() {
@@ -77,10 +117,25 @@ class WithInstantSearch extends Component {
} }
} }
WithInstantSearch.displayName = 'WithInstantSearch'; InstantSearchRoot.displayName = 'InstantSearchRoot';
WithInstantSearch.propTypes = propTypes; InstantSearchRoot.propTypes = propTypes;
export default connect( const InstantSearchRootConnected = connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(WithInstantSearch); )(InstantSearchRoot);
const WithInstantSearch = ({ children }) => (
<Location>
{({ location }) => (
<InstantSearchRootConnected location={location}>
{children}
</InstantSearchRootConnected>
)}
</Location>
);
WithInstantSearch.displayName = 'WithInstantSearch';
WithInstantSearch.propTypes = { children: PropTypes.any };
export default WithInstantSearch;

View File

@@ -85,7 +85,7 @@ class SearchBar extends Component {
if (query) { if (query) {
updateSearchQuery(query); updateSearchQuery(query);
} }
return navigate('/search'); return navigate(`/search${query ? `?query=${query}` : ''}`);
} }
render() { render() {

View File

@@ -1,5 +1,6 @@
import React, { Fragment, Component } from 'react'; import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Index, PoweredBy } from 'react-instantsearch-dom'; import { Index, PoweredBy } from 'react-instantsearch-dom';
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap'; import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
@@ -18,6 +19,7 @@ class SearchPage extends Component {
render() { render() {
return ( return (
<Fragment> <Fragment>
<Helmet title='Search | freeCodeCamp.org' />
<Index indexName='challenges' /> <Index indexName='challenges' />
<Index indexName='guides' /> <Index indexName='guides' />
<Index indexName='youtube' /> <Index indexName='youtube' />