React Gallery: Part One
I recently went through some tech screens that involved building something in React while being recorded, using a browser-based live-code style IDE.
Many of these challenges seem to be very similar. I decided it would be worthwhile to complete one without the time pressure and impending judgement in order to get myself sorted before the next one comes up. Each step after Step Zero has a corresponding branch on Github.
Step Zero: the instructions/requirements
Reading the instructions to the very end is important. There are probably details in the final steps that are designed to test your experience with defining components at the beginning.
Build out the components and get a wireframe of your solution loading first. Mock data if you need to. I spent too much time getting a fetch to return data. This may have been a glitch or deliberate to see how I handled a problem. If I had mocked everything first probably would have come together better at the end.
The instructions I had always started with making a GET call to a free API somewhere:
https://dog.ceo/api/breeds/list/all
Gets a list of dog breeds. Use this list to create a series of calls to this endpoint to get images:
https://dog.ceo/api/breed/breedname/images/random
- Show “Loading” until all the images are loaded
- Display the images in a gallery.
- Show a tooltip on the lower Left when hovering each image that shows the name of that dog breed.
- Use very specific classNames for certain components (which we are told will be used to auto-grade the solution)
- Document any assumptions in comments.
Step One: Plan a Data Model and Build a Wireframe
Each image will need a breed name and breed random image URL. An image container could use props to provide this to enable image display and tooltip showing breed name.
I mocked a simple array of three dog breed names in Gallery and used it to map three Image divs:
Since the sample result image showed nine images in what looked like a flexbox arrangement, I went back and added more elements. I noticed I wasn’t getting wrapped elements at that point and fixed the css in the Gallery.
Prioritizing a layout where the images are positioned with the bottom left in a consistent location will help with locating the tooltips. This might have been the ideal time to build them into the design from the mock data but I was anxious to get actual data and held off. Github Branch
Branch 1 Live:
Step Two: Hit a Service
The first service returns the list of breeds. This will probably run too fast to test a loader, so I will just hit it and check the network tab for the response. I tried the endpoint from my IDE and then again from the tech screen’s.
Don’t be surprised if fetch works locally but not on the tech screen. I never figured out why it wouldn’t respond in the screen, since I didn’t have access to devtools. That is when I should have started mocking based on the responses I was getting locally. Or trying CORS workarounds like adding "proxy": "http://your-company-api-domain"
to package.json and headers
"access-control-allow-origin" : "*",
"Content-type": "application/json; charset=UTF-8"
Anyway, locally the service returned an object containing two properties: message and status. Message contained an array of objects consisting of the breed name as key and an empty array as value.
How will we efficiently transform this into a usable array? I used Object.keys on message and assigned it to a state I named breeds:
const [breeds, setBreeds] = useState([]);
const fetchBreeds = async () => {
const response = await fetch(breedsEndPoint);
let breeds = await response.json();
breeds = Object.keys(breeds.message);
setBreeds(breeds);
}
Github Branch 2-wireframe-breed-names
Branch 2 Live:
Step Three: More Services and Transformations
Now that the breed names are loaded and displayed in the image divs, I can create the requests to get the random dog pictures and I am almost done… I will use concatenation of the endpoint over a map function to do that. Not so fast – these endpoints only return the path to the image: I going to have to resolve the returned image links to actually get the images.
To create the random image URL requests I created a helper. Not really necessary but an opportunity to show that I think about where to put code and reusability and such.
const getURIs = (names) => {
// 'https://dog.ceo/api/breed/affenpinscher/images/random'
const URIs = names.map((dogBreedName) => {
return `https://dog.ceo/api/breed/${dogBreedName}/images/random`;
})
return URIs;
};
export default getURIs;
There are 98 breeds of dogs returned by the API. I will need to stage the requests in a nested series of async/await functions triggered by the page loading. This means using one useEffect to do it all. Once fetchBreeds returns the array of key names and they are processed by my helper into URLs, I will call requestRandomPicURLs. What was fetchBreeds shown above now looks like this:
useEffect(() => {
fetchBreeds().then((data) => {
requestRandomPicURLs(getURIs(data)).then((data) => {
setImageURLs(data);
});
});
}, []);
This is the first part of the challenge that is a bit tricky – we need to know when all the URLs are returned, but we are requesting them one-at-a-time. This requires using Promise.all to gather up all the results while we “await”.
const requestRandomPicURLs = async () => {
const links = await Promise.all(
imageRequests.map(async (url) => {
const resp = await fetch(url);
return resp.json();
})
);
const imageURLs = links.map((link) => {
return link.message;
});
setImageList(imageURLs);
};
I added another state hook imageURLs as an array. For now setImageURLs(data) stores the return random pic URLs. I decided to add them to the Image component to capture my progress. Github Branch 3-wireframe-breed-and-URLs
Step Four: Image Loading
Now that we have all the image URLs we can add an img tag to the Image component and see them. To do this I need to change the useEffect hook again:
useEffect(() => {
fetchBreeds().then((data) => {
requestRandomPicURLs(getURIs(data)).then((data) => {
fetchImages(data).then((data) => {
setImages(data);
});
});
});
}, []);
Our imageURLs state hook is no longer needed because now we pass those URLs as data to fetchImages. Now we need a state for images, so I renamed imageURL state hook:
const [breeds, setBreeds] = useState([]);
const [images, setImages] = useState([]);
We still need breeds because we will still send the breed name to each image in order to populate the tooltip we need to add in a future step. We can also use it to provide a key. Github Branch 4-wireframe-load-images