Skip to content

Instantly share code, notes, and snippets.

@ryanflorence
Last active August 17, 2019 20:08
Show Gist options
  • Save ryanflorence/3042134bfe5c3121c8fc93db7a62bcbc to your computer and use it in GitHub Desktop.
Save ryanflorence/3042134bfe5c3121c8fc93db7a62bcbc to your computer and use it in GitHub Desktop.
import React, { useState, useEffect } from "react";
import "./packages/combobox/styles.css";
import {
Combobox,
ComboboxInput,
ComboboxList,
ComboboxOption,
ComboboxPopup
} from "./packages/combobox/index";
function ServerCitySearch() {
const [searchTerm, setSearchTerm] = useState("");
const cities = useCitySearch(searchTerm);
const handleSearchTermChange = event => setSearchTerm(event.target.value);
return (
<div style={{ maxWidth: 600, margin: "auto" }}>
<h2>Server City Search</h2>
<Combobox ariaLabel="City Search">
<ComboboxInput onChange={handleSearchTermChange} />
<ComboboxPopup>
<RandomFocasableJunk cities={cities} />
<ComboboxList>
{cities.map(city => {
let str = `${city.city}, ${city.state}`;
return <ComboboxOption key={str} value={str} />;
})}
</ComboboxList>
</ComboboxPopup>
</Combobox>
</div>
);
}
function useCitySearch(searchTerm) {
const [cities, setCities] = useState([]);
useEffect(
() => {
if (searchTerm.trim() !== "") {
let current = true;
fetchCities(searchTerm).then(cities => {
if (current) {
setCities(cities);
}
});
return () => (current = false);
}
},
[searchTerm]
);
return cities;
}
function RandomFocasableJunk({ cities }) {
return (
<p
style={{
textAlign: "center",
padding: "5px",
margin: "0",
color: "#888",
fontStyle: "italic"
}}
>
{cities.length} results <a href="https://google.com">Google</a>{" "}
<button>Dangit</button>
</p>
);
}
async function fetchCities(value) {
try {
let res = await fetch(`http://localhost:5000/?${value}`);
return await res.json();
} catch (e) {
throw e;
}
}
export default ServerCitySearch;
import React, { useState, useMemo } from "react";
import "./packages/combobox/styles.css";
import {
Combobox,
ComboboxInput,
ComboboxList,
ComboboxOption,
ComboboxPopup
} from "./packages/combobox/index";
import cities from "./cities";
import matchSorter from "match-sorter";
import { useThrottle } from "use-throttle";
export default function ClientSearch() {
let [term, setTerm] = useState("");
let throttledTerm = useThrottle(term, 100);
let results = useMemo(
() =>
term.trim() === ""
? null
: matchSorter(cities, term, {
keys: [item => `${item.city}, ${item.state}`]
}),
[throttledTerm]
);
const handleChange = event => setTerm(event.target.value);
return (
<div style={{ maxWidth: 600, margin: "auto" }}>
<h2 id="demo">Clientside Search</h2>
<Combobox ariaLabel="Cities">
<ComboboxInput onChange={handleChange} autocompleteOnNav={false} />
<ComboboxPopup>
{results && (
<ComboboxList>
{results.slice(0, 10).map((result, index) => (
<ComboboxOption
key={index}
value={`${result.city}, ${result.state}`}
/>
))}
</ComboboxList>
)}
</ComboboxPopup>
</Combobox>
</div>
);
}
import React, { useState, useEffect } from "react";
import "./packages/combobox/styles.css";
import {
Combobox,
ComboboxInput,
ComboboxList,
ComboboxOption,
ComboboxOptionText,
ComboboxPopup
} from "./packages/combobox/index";
import { useThrottle } from "use-throttle";
import uniqBy from "lodash.uniqby";
const styles = {
option: {
display: "flex",
alignItems: "center",
paddingRight: 10
},
imageWrapper: {
background: "#eee",
width: 30,
height: 30,
textAlign: "center"
},
optionTextWrapper: {
marginLeft: 10,
whiteSpace: "nowrap",
textOverflow: "ellipsis",
overflow: "hidden"
}
};
export default function ITunes() {
const [searchTerm, setSearchTerm] = useState(null);
const songs = useSongSearch(searchTerm);
const handleSearchTermChange = event => setSearchTerm(event.target.value);
return (
<div style={{ maxWidth: 600, margin: "auto" }}>
<h2>iTunes Search</h2>
<Combobox ariaLabel="iTunes Search">
<ComboboxInput onChange={handleSearchTermChange} />
<ComboboxPopup>
<ComboboxList>
{songs &&
songs.map(song => (
<ComboboxOption
key={song.trackId}
value={`${song.trackName}: ${song.collectionName}, ${
song.artistName
}`}
style={styles.option}
>
<div style={styles.imageWrapper}>
<img
alt={song.collectionName}
src={song.artworkUrl60}
height="30"
width="30"
/>
</div>
<div style={styles.optionTextWrapper}>
<ComboboxOptionText />
</div>
</ComboboxOption>
))}
</ComboboxList>
</ComboboxPopup>
</Combobox>
</div>
);
}
async function fetchSongs(value) {
try {
let res = await fetch(`https://itunes.now.sh/?term=${value}`);
let json = await res.json();
let songs = json.results.filter(song => song.trackName);
let unique = uniqBy(
songs,
song => `${song.trackName}, ${song.collectionName}`
);
return unique.slice(0, 10);
} catch (e) {
return Promise.resolve(null);
}
}
function useSongSearch(searchTerm) {
const [results, setResults] = useState([]);
const throttled = useThrottle(searchTerm, 500);
useEffect(
() => {
if (throttled) {
if (throttled.trim() !== "") {
let current = true;
console.log("fetching", throttled);
fetchSongs(throttled).then(results => {
console.log("fetch finished", throttled);
if (current) {
console.log("setting results", throttled);
setResults(results);
}
});
return () => (current = false);
}
}
},
[throttled]
);
return results;
}
@aaron-sf
Copy link

RandomFocasableJunk -> RandomFocusableJunk :)

@shrmn
Copy link

shrmn commented Apr 30, 2019

For reference in the other direction, Ryan's video demo of the above code can be found here.

@pspeter3
Copy link

pspeter3 commented May 7, 2019

Is this going to be part of the reach-ui packages?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment