Skip to content

Commit

Permalink
useDeferredValue(isAvailabilityEnabled) with spinner to reduce page f…
Browse files Browse the repository at this point in the history
…reezing
  • Loading branch information
taneliang committed Dec 18, 2020
1 parent c7d2348 commit 07601ec
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 17 deletions.
8 changes: 4 additions & 4 deletions website/src/views/components/SearchBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ import styles from './SearchBox.scss';
export type Props = {
className?: string;
throttle: number;
isLoading: boolean;
isLoading?: boolean;
value: string | null;
placeholder?: string;
onChange: (value: string) => void;
onSearch: () => void;
onSearch?: () => void;
onBlur?: () => void;
};

const SearchBox: FC<Props> = ({
className,
throttle,
isLoading,
isLoading = false,
value,
placeholder,
onChange,
Expand Down Expand Up @@ -55,7 +55,7 @@ const SearchBox: FC<Props> = ({
debounce(
() => {
isDirty.current = false;
onSearch();
onSearch?.();
},
throttle,
{ leading: false },
Expand Down
2 changes: 1 addition & 1 deletion website/src/views/venues/AvailabilitySearch.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defaultSearchOptions } from 'views/venues/AvailabilitySearch';

describe('defaultSearchOptions', () => {
describe(defaultSearchOptions, () => {
test('should the nearest slots during school hours', () => {
// Monday
expect(defaultSearchOptions(new Date('2018-01-15T12:30:00'))).toMatchObject({
Expand Down
11 changes: 11 additions & 0 deletions website/src/views/venues/VenuesContainer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ $venue-list-width: 16rem;
border-radius: 0 0 $border-radius $border-radius;
}

.availabilitySpinner {
display: inline-block;
width: 1.4rem;
height: 1.4rem;
margin: 0 0.3rem -0.1rem 0;
border-width: 0.2rem;
// Use parent button's text color so that the spinner is still visible when
// the button is hovered over.
border-left-color: initial;
}

.noVenueSelected {
$icon-size: 6rem;

Expand Down
33 changes: 21 additions & 12 deletions website/src/views/venues/VenuesContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Location, locationsAreEqual } from 'history';
import classnames from 'classnames';
import axios from 'axios';
import qs from 'query-string';
import { isEqual, mapValues, noop, pick, size } from 'lodash';
import { isEqual, mapValues, pick, size } from 'lodash';

import type { TimePeriod, Venue, VenueDetailList, VenueSearchOptions } from 'types/venues';
import type { Subtract } from 'types/utils';
Expand Down Expand Up @@ -53,18 +53,20 @@ export const VenuesContainerComponent: FC<Props> = ({ venues }) => {
const location = useLocation();
const matchParams = useParams<Params>();

// Search state
const [
/** Value of the controlled search box; updated real-time */
searchQuery,
setSearchQuery,
] = useState<string>(() => qs.parse(location.search).q || '');
/** Actual string to search with; deferred update */
const deferredSearchQuery = useDeferredValue(searchQuery);

const [isAvailabilityEnabled, setIsAvailabilityEnabled] = useState(() => {
const params = qs.parse(location.search);
return !!(params.time && params.day && params.duration);
});
const deferredIsAvailabilityEnabled = useDeferredValue(isAvailabilityEnabled);

const [searchOptions, setSearchOptions] = useState(() => {
const params = qs.parse(location.search);
// Extract searchOptions from the query string if they are present
Expand Down Expand Up @@ -103,14 +105,19 @@ export const VenuesContainerComponent: FC<Props> = ({ venues }) => {
);

const highlightPeriod = useMemo<TimePeriod | undefined>(() => {
if (!isAvailabilityEnabled) return undefined;
if (!deferredIsAvailabilityEnabled) return undefined;

return {
day: searchOptions.day,
startTime: convertIndexToTime(searchOptions.time * 2),
endTime: convertIndexToTime(2 * (searchOptions.time + searchOptions.duration)),
};
}, [isAvailabilityEnabled, searchOptions.day, searchOptions.duration, searchOptions.time]);
}, [
deferredIsAvailabilityEnabled,
searchOptions.day,
searchOptions.duration,
searchOptions.time,
]);

const selectedVenue = useMemo<Venue | undefined>(
() => (matchParams.venue ? decodeURIComponent(matchParams.venue) : undefined),
Expand All @@ -121,7 +128,7 @@ export const VenuesContainerComponent: FC<Props> = ({ venues }) => {
useEffect(() => {
let query: Partial<Params> = {};
if (deferredSearchQuery) query.q = deferredSearchQuery;
if (isAvailabilityEnabled) query = { ...query, ...searchOptions };
if (deferredIsAvailabilityEnabled) query = { ...query, ...searchOptions };
const search = qs.stringify(query);

const pathname = venuePage(selectedVenue);
Expand All @@ -139,17 +146,17 @@ export const VenuesContainerComponent: FC<Props> = ({ venues }) => {
}
}, [
debouncedHistory,
deferredIsAvailabilityEnabled,
deferredSearchQuery,
history,
isAvailabilityEnabled,
searchOptions,
selectedVenue,
]);

const matchedVenues = useMemo(() => {
const matched = searchVenue(venues, deferredSearchQuery);
return isAvailabilityEnabled ? filterAvailability(matched, searchOptions) : matched;
}, [isAvailabilityEnabled, searchOptions, deferredSearchQuery, venues]);
return deferredIsAvailabilityEnabled ? filterAvailability(matched, searchOptions) : matched;
}, [deferredIsAvailabilityEnabled, searchOptions, deferredSearchQuery, venues]);
const matchedVenueNames = useMemo(() => matchedVenues.map(([venue]) => venue), [matchedVenues]);

function renderSearch() {
Expand All @@ -160,23 +167,25 @@ export const VenuesContainerComponent: FC<Props> = ({ venues }) => {
<SearchBox
className={styles.searchBox}
throttle={0}
isLoading={false}
value={searchQuery}
placeholder="e.g. LT27"
onChange={setSearchQuery}
onSearch={noop}
/>

<button
className={classnames(
'btn btn-block btn-svg',
styles.availabilityToggle,
isAvailabilityEnabled ? 'btn-primary' : 'btn-outline-primary',
)}
onClick={onFindFreeRoomsClicked}
type="button"
>
<Clock className="svg" /> Find free rooms
{isAvailabilityEnabled !== deferredIsAvailabilityEnabled ? (
<LoadingSpinner className={styles.availabilitySpinner} white={isAvailabilityEnabled} />
) : (
<Clock className="svg" />
)}{' '}
Find free rooms
</button>

{isAvailabilityEnabled && (
Expand Down

0 comments on commit 07601ec

Please sign in to comment.