Image Viewer Tab
Projects Involved: GCS
Concepts: React, Typescript, CSS, HTTP Handling
Abstract:
Create a new tab in the React frontend that fetches and displays an image from the existing Go backend. This task introduces essential React hooks and state management, basic TypeScript typing, CSS styling, and a simple HTTP request flow. Use StatusPage
and PlaygroundPage
as references for structure, routing, and styling conventions.
Goals:
- Learn some basic React
- Be able to create new Frontend components for GCS features
Files Involved:
GCS
Reference files:
src/pages/StatusPage.tsx
(HTTP Handling)src/pages/PlaygroundPage.tsx
(Clean examples of hooks and state management)
Files to create/work on:
src/pages/ImagePage.tsx
(new page)src/styles/ImagePage.css
(optional; you may do the css inline as well)src/App.tsx
(tabs are registered here)
Base Steps:
1. Explore StatusPage
and PlaygroundPage
These existing pages are written so you can understand how tabs are structured, routed, and styled. Scaffold a new tab that follows similar conventions.
2. Write a new tab
Try copying the existing logic by adding a new Tab and verifying the tab works on click. This step should be trivial just by looking at how other tabs are wired.
3. Add React state
There a couple react states that are required for each page. One for loading, error, and image data. Use an effect to trigger a fetch when the page mounts or when a refresh action occurs.
Refer to hints if you are stuck
4. Implement a GET request
Recall the overview of the API lifecycle from task 7. You need to implement the /capture
GET request to the Go endpoint you implemented, which should provide an image in base64. Parse the response appropriately and display the image. Show loading and error states.
5. Define TypeScript types
Define TypeScript types for the response you expect. Keep props and component state typed and avoid any
.
6. Add some basic CSS
Add some basic CSS that aligns with the existing design and make sure the image scales well and has sensible max dimensions.
7. Run the code
Manually verify the full flow: loading indicator, image renders, errors handled gracefully, and retry/refresh works.
Tips:
1: If you are new to react...
-
If you are new to React, you might find these articles on basic hooks and state management useful. Specifically,
useState
will be the most relevant here.
2: To display the image...
You don't need to do any fancy magic to convert the base64 string into an actual image. Simply using the img tag like below is enough.3: If you are completely lost...
Use the boilerplate below as a starting point, and fill out the TODOs. Look at StatusPage.tsx, there are two API calls there that might be relevant.import { useState } from "react";
import "./ImagePage.css";
const ImagePage = () => {
const [imageB64, setImageB64] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const fetchImage = () => {
setIsLoading(true);
setError(null);
setImageB64(null);
fetch(`/api/v1/obc/capture`)
.then(async (response) => {
if (!response.ok) {
// TODO
}
return response.text();
})
.then((text: string) => {
// TODO
})
.catch((err: Error) => {
// TODO
})
.finally(() => {
// TODO
});
};
const handleRefresh = () => {
fetchImage();
};
const dataUrl = imageB64 ? `data:image/png;base64,${imageB64}` : null;
return (
<div style={{ fontFamily: "sans-serif", padding: "20px" }}>
<h2>Image Viewer</h2>
<div style={{ display: "flex", gap: 10, alignItems: "center" }}>
<button onClick={handleRefresh} disabled={isLoading}>
{isLoading ? "Loading..." : "Load / Reload Image"}
</button>
</div>
<section
style={{
marginTop: "20px",
padding: "12px",
border: "1px solid #ccc",
borderRadius: 6,
backgroundColor: "#f9f9f9",
minHeight: 200,
}}
>
<h3 style={{ marginTop: 0 }}>Preview</h3>
{isLoading && <p>Fetching image from backend...</p>}
{error && (
<div className="error-box">
<h4>Error</h4>
<pre style={{ whiteSpace: "pre-wrap" }}>{error}</pre>
<button onClick={handleRefresh} disabled={isLoading}>
Retry
</button>
</div>
)}
{!isLoading && !error && !dataUrl && (
<p>No image yet. Click Refresh Image to fetch.</p>
)}
{dataUrl && !error && (
<div className="image-container">
<img
src={dataUrl}
alt="Fetched from backend"
className="image-preview"
/>
</div>
)}
</section>
</div>
);
};
export default ImagePage;