From 8e9b2ed74ad50d7e1e3f8697c632d54b22c9440a Mon Sep 17 00:00:00 2001 From: Andy Wu Date: Sun, 28 Aug 2022 21:25:51 -0400 Subject: [PATCH] Cleaned up directory structure and added error messages for both query pages --- app/frontend/src/App.js | 153 +------------------ app/frontend/src/App.test.js | 8 - app/frontend/src/routes/QueryBreedSection.js | 65 ++++++++ app/frontend/src/routes/RandomDogImage.js | 29 ++++ app/frontend/src/setupTests.js | 5 - app/frontend/src/util.js | 66 ++++++++ 6 files changed, 166 insertions(+), 160 deletions(-) delete mode 100644 app/frontend/src/App.test.js create mode 100644 app/frontend/src/routes/QueryBreedSection.js create mode 100644 app/frontend/src/routes/RandomDogImage.js delete mode 100644 app/frontend/src/setupTests.js diff --git a/app/frontend/src/App.js b/app/frontend/src/App.js index c542fca..c6131ff 100644 --- a/app/frontend/src/App.js +++ b/app/frontend/src/App.js @@ -1,9 +1,9 @@ -import React, { useEffect, useState } from 'react'; import { Routes, Route } from 'react-router-dom'; import './styles/style.css'; import './styles/layout.css'; -import {getRandomIntInRange, queryOptionsToHTML} from './util.js'; +import { RandomDogImage } from './routes/RandomDogImage'; +import { QueryBreedSection } from './routes/QueryBreedSection'; function App() { @@ -15,16 +15,16 @@ function App() { The same images then load successfully immediately afterward. NOTE: Error seems to only be occurring on Firefox */} } /> - } /> - } /> + } /> + } /> {/*
*/}

@@ -37,145 +37,4 @@ function Home() { ); } -// Used as parent of components that need to render data after fetching -// but difficult to manage own state or need to render using fetched data -// -// Since fetch is async, guaranteeing data received before render function is -// called is impossible in the same function, -// so this component handles state of said data and -// conditionally renders 'ComponentToRender' only when that data is received. -function DataFetcher({url, ComponentToRender}) { - const [data, setData] = useState(); - useEffect(() => { - let ignore = false; - - const fetchData = async () => { - const response = await fetch(url); - if(!ignore) { - const json = await response.json(); - setData(json); - } - } - - fetchData(); - - return () => { - ignore = true; - }; - }, [url]); - - return data && -} - -function DogQueryForm({queryOptions}) { - const [posted, setPosted] = useState(false); - const [imageLinks, setImageLinks] = useState([]); - - const breedOptions = queryOptionsToHTML(queryOptions); - // Config number of images to pull from API - const imageCount = 27; - - // TODO: Find way to stop user from spamming fetches - function handleSubmit(e) { - e.preventDefault(); - setPosted(true); - const breed = e.target.breeds.value.trim().toLowerCase(); - - if(breed !== undefined && breed.length > 0) { - fetch(`http://localhost:3011/dog/${breed}/get-images/${imageCount}`) - .then(response => response.json()) - .then(data => setImageLinks(data['message'])) - .catch(error => { - setImageLinks([]); - }) - } - } - - let imageSectionHTML =

Select a name and click 'Fetch' to get started!

; - if(posted) { - if(imageLinks.length > 0) { - imageSectionHTML = ; - } - else { - imageSectionHTML =

Invalid breed name!

; - } - } - - return ( -
-

Lots of dogs! 🐕

-

See {imageCount} random photos of your favorite dogs

-
- - - -
-
- {imageSectionHTML} -
- ); -} - -function QueryBreedSection() { - return ( - <> -
- -
- - ); -} - -function RandomDogImage() { - const [imageLink, setImageLink] = useState(''); - - function fetchImage() { - fetch('http://localhost:3011/dog/get-random') - .then(response => response.json()) - .then(data => setImageLink(data['message'])) - .catch(error => setImageLink('')); - }; - - return ( -
-

Random Dog Image

-

a new image

- {imageLink && Dog} -
- ); -} - -function ImageList(props) { - const srcListLength = props.images.length; - const renderLength = Math.min(props.desiredLength, srcListLength); - const imageList = []; - - // Track chosen indices to prevent duplicate images from being picked - const availableIdx = [...Array(srcListLength).keys()]; - - for(let i = 0; i < renderLength; i++) { - const randomIndex = getRandomIntInRange(availableIdx.length); - const imageIndex = availableIdx[randomIndex]; - - // Swap last element with chosen element and pop to prevent duplicate copies - availableIdx[randomIndex] = availableIdx[availableIdx.length - 1] - availableIdx.pop(); - - const imgSrc = props.images[imageIndex]; - const img = Dog; - imageList.push(img); - - } - - // console.log(imageList); - return ( -
- {imageList} -
- ); -} - export default App; diff --git a/app/frontend/src/App.test.js b/app/frontend/src/App.test.js deleted file mode 100644 index 1f03afe..0000000 --- a/app/frontend/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/app/frontend/src/routes/QueryBreedSection.js b/app/frontend/src/routes/QueryBreedSection.js new file mode 100644 index 0000000..01babcc --- /dev/null +++ b/app/frontend/src/routes/QueryBreedSection.js @@ -0,0 +1,65 @@ +import React, { useState } from 'react'; + +import { ImageList, queryOptionsToHTML, DataFetcher } from '../util'; + +function DogQueryForm({queryOptions}) { + const [posted, setPosted] = useState(false); + const [imageLinks, setImageLinks] = useState([]); + + const breedOptions = queryOptionsToHTML(queryOptions); + // Config number of images to pull from API + const imageCount = 27; + + // TODO: Find way to stop user from spamming fetches + function handleSubmit(e) { + e.preventDefault(); + setPosted(true); + const breed = e.target.breeds.value.trim().toLowerCase(); + + if(breed !== undefined && breed.length > 0) { + fetch(`http://localhost:3011/dog/${breed}/get-images/${imageCount}`) + .then(response => response.json()) + .then(data => setImageLinks(data['message'])) + .catch(error => { + setImageLinks([]); + }) + } + } + + let imageSectionHTML =

Select a name and click 'Fetch' to get started!

; + if(posted) { + if(imageLinks.length > 0) { + imageSectionHTML = ; + } + else { + imageSectionHTML =

Invalid breed name!

; + } + } + + return ( +
+

Lots of dogs! 🐕

+

See {imageCount} random photos of your favorite dogs

+
+ + + +
+
+ {imageSectionHTML} +
+ ); +} + +export function QueryBreedSection() { + return ( + <> +
+ +
+ + ); +} diff --git a/app/frontend/src/routes/RandomDogImage.js b/app/frontend/src/routes/RandomDogImage.js new file mode 100644 index 0000000..1b062f9 --- /dev/null +++ b/app/frontend/src/routes/RandomDogImage.js @@ -0,0 +1,29 @@ +import React, { useState } from 'react'; + + +export function RandomDogImage() { + const [posted, setPosted] = useState(null); + const [imageLink, setImageLink] = useState(''); + + function fetchImage() { + fetch('http://localhost:3011/dog/get-random') + .then(response => response.json()) + .then(data => { + setImageLink(data['message']); + setPosted(true); + }) + .catch(error => { + setImageLink(''); + setPosted(false); + }); + }; + + return ( +
+

Random Dog Image

+

a new image

+ {posted === false &&

Error fetching image from server!

} + {posted && Dog} +
+ ); +} diff --git a/app/frontend/src/setupTests.js b/app/frontend/src/setupTests.js deleted file mode 100644 index 8f2609b..0000000 --- a/app/frontend/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; diff --git a/app/frontend/src/util.js b/app/frontend/src/util.js index cbe6fec..ec0d830 100644 --- a/app/frontend/src/util.js +++ b/app/frontend/src/util.js @@ -1,4 +1,6 @@ +import React, { useEffect, useState } from 'react'; +// Functional helpers export function getRandomIntInRange(rangeEnd) { return Math.floor(Math.random() * rangeEnd); } @@ -31,3 +33,67 @@ export function queryOptionsToHTML(data) { return options; } + +// Component helpers + +// Used as parent of components that need to render data after fetching +// but difficult to manage own state or need to render using fetched data +// +// Since fetch is async, guaranteeing data received before render function is +// called is impossible in the same function, +// so this component handles state of said data and +// conditionally renders 'ComponentToRender' only when that data is received. +export function DataFetcher({url, ComponentToRender}) { + const [data, setData] = useState(null); + useEffect(() => { + + const fetchData = async () => { + const response = await fetch(url); + const json = await response.json(); + setData(json); + } + + fetchData(); + + }, [url]); + + if(data == null) { + return ( + <> +

Fetching page data

+

If this page doesn't load by itself, there may be an issue with the server

+ + ) + } + return data && + } + +export function ImageList(props) { + const srcListLength = props.images.length; + const renderLength = Math.min(props.desiredLength, srcListLength); + const imageList = []; + + // Track chosen indices to prevent duplicate images from being picked + const availableIdx = [...Array(srcListLength).keys()]; + + for(let i = 0; i < renderLength; i++) { + const randomIndex = getRandomIntInRange(availableIdx.length); + const imageIndex = availableIdx[randomIndex]; + + // Swap last element with chosen element and pop to prevent duplicate copies + availableIdx[randomIndex] = availableIdx[availableIdx.length - 1] + availableIdx.pop(); + + const imgSrc = props.images[imageIndex]; + const img = Dog; + imageList.push(img); + + } + + // console.log(imageList); + return ( +
+ {imageList} +
+ ); + }