Move state to App component
This commit is contained in:
parent
035eb2e548
commit
ce1bd7de40
@ -1,7 +1,83 @@
|
|||||||
import React from 'react';
|
import React, { useState, useCallback, useEffect } from 'react';
|
||||||
import { Grommet } from 'grommet';
|
import { Grommet } from 'grommet';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { gql, useLazyQuery } from '@apollo/client';
|
||||||
|
|
||||||
import { Header } from './Header';
|
import { Header } from './Header';
|
||||||
import { SearchBox } from './SearchBox';
|
import { Search } from './Search';
|
||||||
|
import { Results } from './Results';
|
||||||
|
import { Artists, Artist } from '../interfaces';
|
||||||
|
|
||||||
|
const QUERY_ARTIST_ALBUMS = gql`
|
||||||
|
query Artist($byName: String!) {
|
||||||
|
queryArtists(byName: $byName) {
|
||||||
|
name
|
||||||
|
image
|
||||||
|
id
|
||||||
|
albums {
|
||||||
|
name
|
||||||
|
image
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
const [getArtists, { data }] = useLazyQuery(QUERY_ARTIST_ALBUMS);
|
||||||
|
const [artists, setArtists] = useState<Artists | undefined>(undefined);
|
||||||
|
const [suggestions, setSuggestions] = useState<string[] | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Debounce the database query
|
||||||
|
// See: https://archive.is/wip/6JDqb
|
||||||
|
const updateQuery = () => {
|
||||||
|
getArtists({ variables: { byName: inputValue } });
|
||||||
|
};
|
||||||
|
|
||||||
|
const delayedQuery = useCallback(debounce(updateQuery, 500), [inputValue]);
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setInputValue(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
delayedQuery();
|
||||||
|
// Cancel previous debounce calls during useEffect cleanup.
|
||||||
|
return delayedQuery.cancel;
|
||||||
|
}, [inputValue, delayedQuery]);
|
||||||
|
|
||||||
|
// TODO: Maybe merge the two use effects?
|
||||||
|
useEffect(() => {
|
||||||
|
if (data && data.queryArtists !== []) {
|
||||||
|
// Limit artists to 5
|
||||||
|
const updatedArtists = data.queryArtists.slice(0, 5);
|
||||||
|
const updatedSuggestions: string[] = updatedArtists.map(
|
||||||
|
(el: Artist) => {
|
||||||
|
return el.name;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
setSuggestions(updatedSuggestions);
|
||||||
|
setArtists(updatedArtists);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grommet theme={theme}>
|
||||||
|
<Header>
|
||||||
|
<h1>Spoti Search</h1>
|
||||||
|
</Header>
|
||||||
|
<Search
|
||||||
|
inputValue={inputValue}
|
||||||
|
handleChange={handleChange}
|
||||||
|
suggestions={suggestions}
|
||||||
|
/>
|
||||||
|
<Results />
|
||||||
|
</Grommet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const theme = {
|
const theme = {
|
||||||
global: {
|
global: {
|
||||||
@ -12,16 +88,3 @@ const theme = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function App() {
|
|
||||||
return (
|
|
||||||
<Grommet theme={theme}>
|
|
||||||
<Header>
|
|
||||||
<h1>Spoti Search</h1>
|
|
||||||
</Header>
|
|
||||||
<SearchBox />
|
|
||||||
</Grommet>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
14
src/components/Results.tsx
Normal file
14
src/components/Results.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React, { PropsWithChildren } from 'react';
|
||||||
|
import { Box } from 'grommet';
|
||||||
|
|
||||||
|
export const Results = (props: PropsWithChildren<{}>) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as='section'
|
||||||
|
direction='row'
|
||||||
|
justify='center'
|
||||||
|
margin={{ vertical: 'large' }}
|
||||||
|
>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
29
src/components/Search.tsx
Normal file
29
src/components/Search.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, TextInput } from 'grommet';
|
||||||
|
import { FormSearch } from 'grommet-icons';
|
||||||
|
|
||||||
|
export const Search = ({
|
||||||
|
inputValue,
|
||||||
|
handleChange,
|
||||||
|
suggestions,
|
||||||
|
}: {
|
||||||
|
inputValue: string;
|
||||||
|
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
suggestions: string[] | undefined;
|
||||||
|
}) => (
|
||||||
|
<Box
|
||||||
|
as='section'
|
||||||
|
direction='row'
|
||||||
|
justify='center'
|
||||||
|
margin={{ vertical: 'large' }}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
value={inputValue}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder='Type an artist name'
|
||||||
|
icon={<FormSearch color='plain' />}
|
||||||
|
dropHeight='large'
|
||||||
|
suggestions={suggestions}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
@ -1,82 +0,0 @@
|
|||||||
import React, { useState, useCallback, useEffect } from 'react';
|
|
||||||
import { gql, useLazyQuery } from '@apollo/client';
|
|
||||||
import { Box, TextInput } from 'grommet';
|
|
||||||
import { FormSearch } from 'grommet-icons';
|
|
||||||
import { debounce } from 'lodash';
|
|
||||||
|
|
||||||
import { Artists, Artist } from '../interfaces';
|
|
||||||
|
|
||||||
const QUERY_ARTIST_ALBUMS = gql`
|
|
||||||
query Artist($byName: String!) {
|
|
||||||
queryArtists(byName: $byName) {
|
|
||||||
name
|
|
||||||
image
|
|
||||||
id
|
|
||||||
albums {
|
|
||||||
name
|
|
||||||
image
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SearchBox = () => {
|
|
||||||
const [inputValue, setInputValue] = useState('');
|
|
||||||
const [getArtists, { data }] = useLazyQuery(QUERY_ARTIST_ALBUMS);
|
|
||||||
const [artists, setArtists] = useState<Artists | undefined>(undefined);
|
|
||||||
const [suggestions, setSuggestions] = useState<string[] | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Debounce the database query, based on the following article:
|
|
||||||
// https://dev.to/reflexgravity/use-lodash-debouce-inside-a-functional-component-in-react-4g5j
|
|
||||||
const updateQuery = () => {
|
|
||||||
getArtists({ variables: { byName: inputValue } });
|
|
||||||
};
|
|
||||||
|
|
||||||
const delayedQuery = useCallback(debounce(updateQuery, 500), [inputValue]);
|
|
||||||
|
|
||||||
const handleChange = (e: any) => {
|
|
||||||
setInputValue(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
delayedQuery();
|
|
||||||
// Cancel previous debounce calls during useEffect cleanup.
|
|
||||||
return delayedQuery.cancel;
|
|
||||||
}, [inputValue, delayedQuery]);
|
|
||||||
|
|
||||||
// TODO: Maybe merge the two use effects?
|
|
||||||
useEffect(() => {
|
|
||||||
if (data && data.queryArtists !== []) {
|
|
||||||
// Limit artists to 5
|
|
||||||
const updatedArtists = data.queryArtists.slice(0, 5);
|
|
||||||
const updatedSuggestions: string[] = updatedArtists.map(
|
|
||||||
(el: Artist) => {
|
|
||||||
return el.name;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
setSuggestions(updatedSuggestions);
|
|
||||||
setArtists(updatedArtists);
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
as='section'
|
|
||||||
direction='row'
|
|
||||||
justify='center'
|
|
||||||
margin={{ vertical: 'large' }}
|
|
||||||
>
|
|
||||||
<TextInput
|
|
||||||
value={inputValue}
|
|
||||||
onChange={handleChange}
|
|
||||||
placeholder='Type an artist name'
|
|
||||||
icon={<FormSearch color='plain' />}
|
|
||||||
dropHeight='large'
|
|
||||||
suggestions={suggestions}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user