feat(client): use url query param on search page
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
aee2f5e536
commit
84c46750e8
@@ -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}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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() {
|
||||||
|
@@ -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' />
|
||||||
|
Reference in New Issue
Block a user