Skip to content

Commit

Permalink
Cleaned up directory structure and added error messages for both quer…
Browse files Browse the repository at this point in the history
…y pages
  • Loading branch information
Andy-Wu12 committed Aug 29, 2022
1 parent d329f52 commit 8e9b2ed
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 160 deletions.
153 changes: 6 additions & 147 deletions app/frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -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() {

Expand All @@ -15,16 +15,16 @@ function App() {
The same images then load successfully immediately afterward.
NOTE: Error seems to only be occurring on Firefox */}
<Route path="/" element={<Home />} />
<Route path="/getBreed" element={<QueryBreedSection />} />
<Route path="/getRandom" element={<RandomDogImage />} />
<Route path="/get-breed" element={<QueryBreedSection />} />
<Route path="/get-random" element={<RandomDogImage />} />
{/* <section id='RandomImage'>
<RandomDogImage />
</section> */}
</Routes>
<div className="navbar">
<a href='/' className='home'> Home </a>
<a href='/getBreed'> Search Breeds </a>
<a href='/getRandom'> Random Dogs </a>
<a href='/get-breed'> Search Breeds </a>
<a href='/get-random'> Random Dogs </a>
</div>
<br/><br/>
</div>
Expand All @@ -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 && <ComponentToRender queryOptions={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 = <p> Select a name and click 'Fetch' to get started! </p>;
if(posted) {
if(imageLinks.length > 0) {
imageSectionHTML = <ImageList images={imageLinks} desiredLength={imageCount} />;
}
else {
imageSectionHTML = <p> Invalid breed name! </p>;
}
}

return (
<div className='query-form'>
<h1>Lots of dogs! 🐕</h1>
<p>See {imageCount} random photos of your favorite dogs</p>
<form onSubmit={handleSubmit}>
<label htmlFor="breeds"> Select a breed: </label>
<select name="breeds" id="breeds">
{breedOptions}
</select>
<button className="fetch" type="submit">Fetch</button>
</form>
<br/>
{imageSectionHTML}
</div>
);
}

function QueryBreedSection() {
return (
<>
<section id='QueryBreedSection'>
<DataFetcher url='http://localhost:3011/breeds'
ComponentToRender={DogQueryForm} />
</section>
</>
);
}

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 (
<div className='random-image-container'>
<h1> Random Dog Image </h1>
<p> <button className='fetch' onClick={fetchImage}>Fetch</button> a new image </p>
{imageLink && <img className='random-dog-image' src={imageLink} alt='Dog' />}
</div>
);
}

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 = <img key={`image${i}`} className='list-dog-image' src={imgSrc} alt='Dog' />;
imageList.push(img);

}

// console.log(imageList);
return (
<div className='dog-images'>
{imageList}
</div>
);
}

export default App;
8 changes: 0 additions & 8 deletions app/frontend/src/App.test.js

This file was deleted.

65 changes: 65 additions & 0 deletions app/frontend/src/routes/QueryBreedSection.js
Original file line number Diff line number Diff line change
@@ -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 = <p> Select a name and click 'Fetch' to get started! </p>;
if(posted) {
if(imageLinks.length > 0) {
imageSectionHTML = <ImageList images={imageLinks} desiredLength={imageCount} />;
}
else {
imageSectionHTML = <p> Invalid breed name! </p>;
}
}

return (
<div className='query-form'>
<h1>Lots of dogs! 🐕</h1>
<p>See {imageCount} random photos of your favorite dogs</p>
<form onSubmit={handleSubmit}>
<label htmlFor="breeds"> Select a breed: </label>
<select name="breeds" id="breeds">
{breedOptions}
</select>
<button className="fetch" type="submit">Fetch</button>
</form>
<br/>
{imageSectionHTML}
</div>
);
}

export function QueryBreedSection() {
return (
<>
<section id='QueryBreedSection'>
<DataFetcher url='http://localhost:3011/breeds'
ComponentToRender={DogQueryForm} />
</section>
</>
);
}
29 changes: 29 additions & 0 deletions app/frontend/src/routes/RandomDogImage.js
Original file line number Diff line number Diff line change
@@ -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 (
<div className='random-image-container'>
<h1> Random Dog Image </h1>
<p> <button className='fetch' onClick={fetchImage}>Fetch</button> a new image </p>
{posted === false && <p> Error fetching image from server! </p> }
{posted && <img className='random-dog-image' src={imageLink} alt='Dog' />}
</div>
);
}
5 changes: 0 additions & 5 deletions app/frontend/src/setupTests.js

This file was deleted.

66 changes: 66 additions & 0 deletions app/frontend/src/util.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, { useEffect, useState } from 'react';

// Functional helpers
export function getRandomIntInRange(rangeEnd) {
return Math.floor(Math.random() * rangeEnd);
}
Expand Down Expand Up @@ -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 (
<>
<h1> Fetching page data </h1>
<p> If this page doesn't load by itself, there may be an issue with the server </p>
</>
)
}
return data && <ComponentToRender queryOptions={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 = <img key={`image${i}`} className='list-dog-image' src={imgSrc} alt='Dog' />;
imageList.push(img);

}

// console.log(imageList);
return (
<div className='dog-images'>
{imageList}
</div>
);
}

0 comments on commit 8e9b2ed

Please sign in to comment.