-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cat application #5
base: main
Are you sure you want to change the base?
Conversation
@viktorntaf what made you use JS over TS? And since you decided to go with that, why not using some basic error handling to counterbalance the lack of type checking? |
If you had to design an actual app that uses such a key, how would you approach that? |
const Navbar = () => { | ||
return ( | ||
<AppBar position="static"> | ||
<Toolbar className=".MuiToolbar-root"> | ||
<Typography variant="h6" style={{ flexGrow: 1 }}> | ||
Cat App | ||
</Typography> | ||
<Button color="inherit" component={Link} to="/">Home</Button> | ||
<Button color="inherit" component={Link} to="/breeds">Breeds</Button> | ||
<Button color="inherit" component={Link} to="/favourites">Favourites</Button> | ||
</Toolbar> | ||
</AppBar> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What kind of improvement could you do with this component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
className=".MuiToolbar-root"
is a mistake since it's not a valid React className, and it's redundant because MUI provides this class by default.- Inline styling isn't a good choice; I would use the sx prop provided by MUI, which I use throughout the rest of the app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, I'd also add that it could be moved to a separate file.
const AppWrapper = () => ( | ||
<Router> | ||
<App /> | ||
</Router> | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just use that in the App component instead of that "div" you used as wrapper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use const location = useLocation();
that needs to be inside a context. So I split the component in order for the useLocation to have access to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Still, this JSX structure looks a bit odd. I haven't followed on the latest react-router version and if it's still supported by the current API, but maybe moving <NavBar />
inside <Route path="/images/:id">
as child would do the trick, or even accessing window.location.path directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, it does feel a bit off. To achieve the same functionality using the children approach, I’d need to place <NavBar />
inside every route except <Route path="/images/:id">
, ensuring it appears on all pages but the modal. However, that would require repeating this for every new route, which isn't ideal for maintainability.
A better alternative would be moving useLocation
inside <NavBar />
to handle conditional rendering directly within the component. This would keep the App.js structure more traditional and cleaner. However, this change would turn the <NavBar />
from a presentational to a functional component, making it more specific to this app's routing logic. As a result, it would lose reusability across other parts of the app, since it’s no longer a general-purpose component.
While this approach sacrifices some flexibility, I agree it could be worth it for simplicity in this specific case.
useEffect(() => { | ||
if (catIsFavourite) { | ||
setIsFavourite(Boolean(catIsFavourite.length)); | ||
} | ||
}, [catIsFavourite]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really need this useEffect here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And why do you need this extra isFavourite in your component's state when you already have this information (catIsFavourite)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API response catIsFavourite
indicates if the user has marked the cat image as a favorite, while isFavorite
is an internal component state used to toggle the checkbox. I could have named it better, like userHasCheckedFavorite
. I designed it this way to avoid calling the isCatFavourite
API each time the checkbox is toggled, optimizing performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really need this useEffect here?
You are correct we can avoid the useEffect here if checkbox is a different component. That way we control the renders of the new checkbox component, thus it's internal state, by passing catIsFavourite?.length
as a prop.
@@ -0,0 +1,95 @@ | |||
import axios from 'axios'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you decide to use axios over the native fetch API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chose axios for its built-in support for timeouts, interceptors, and error handling. It also simplifies tasks like setting headers and parsing JSON. While these features may not be essential for a simple app, I selected it with scalability in mind for future growth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. But are any of these features necessary in your app? I'm trying to understand the gains over, say, ending up with a slightly larger output/bundle file after compiling to JS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the current project it is true that is not needed and I initially used only fetch. I made the switch to axios because:
- I called
POST:https://api.thecatapi.com/v1/favourites
with an existing image id and it returned 400. To me that was an indication that the handling error functionality from AXIOS would come in handy in order to implement both a generic errors handler and specific requests like treating a 400 as non-fatal - Another reason for switching was the POST to https://api.thecatapi.com/v1/images/search, which returned more data than needed. Using Axios’s
transformResponse
would allow to filter the response before passing it to the component.
Also i since it is fairly lightweight in bundle size (35.8 kB Minified) I believed that the future benefits would overscale the extra bundle size.
To be fair, Axios isn't really needed for this project as said earlier, and if I followed the YAGNI approach, I wouldn't have used it. However, I chose it with future growth in mind, anticipating the app will scale and benefit from its features later on.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; | ||
import Modal from 'react-modal'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't help it but wonder why using react-query when you opted for a minimal approach that left out the list of improvements you included in your PR's opening comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially, I didn’t plan to use react-query, but the need for a paginated query on the dashboard made me reconsider, as it provides an out-of-the-box solution for managing server state and pagination. Additionally, when I started with Next.js, react-query was one of the suggested libraries, which influenced my decision.
I initially started with Next.js, a React framework, and TypeScript for SSR benefits (e.g., the favorites page) and potential SEO needs. On Friday, I got feedback that while you'd be okay with Next.js, it wasn’t your preference. In hindsight, I should’ve stuck with Next.js or requested more time. |
I’d store the API key in a .env file for local development, and in a cloud environment, it would live in environment variables. This ensures different keys across environments (dev, QA, prod) and enhances security by not exposing the key in the code. We can also change the key without needing code changes or PRs, making the app more scalable and versatile. |
I see - this covers not using SSR, but TS could be used regardless that, no? I noticed you used annotations in some parts, but they're incomplete and partial to offer type guidance. |
I completely agree with you—it really was just the time constraint that led me to choose JavaScript over TypeScript. In hindsight, it probably wasn’t the best decision, especially since I had already built half the app in TypeScript with Next.js. Implementing TypeScript in the React version likely wouldn’t have taken much more time, and I could have benefited from the type safety it offers. |
This the task that was asked regarding a user seeing cat images, info for each cat, and adding favorite cat images.
Notes
While this a working example with 100% of the functionality there is still room for improvements. Some them are: