Skip to content

Commit

Permalink
astro-pokedex: add grid and detail page
Browse files Browse the repository at this point in the history
  • Loading branch information
paulcpk committed Sep 23, 2023
1 parent c86440b commit ef67912
Show file tree
Hide file tree
Showing 13 changed files with 428 additions and 64 deletions.
11 changes: 11 additions & 0 deletions pokedex-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ Breakdown:

### Astro.js

I had a pretty good time until I tried to implement dynamic routes. Astro's conventions are great for converting static content, like Markdown into HTML + Styles. However it seems to me like just a glorified Blogging engine.

At first, the Markdown flavor of the components and pages makes it feel like everything is just super easy to setup with one line of code. Once you try to do something a bit more complicated, you'll most likely need to write your components in React's JSX/TSX syntax anyway, and you're back to writing a React app with a slightly less sophisticated API for providing data to SSR/SSG rendered components.

See the API: https://docs.astro.build/en/core-concepts/routing/#static-ssg-mode

*Correction*: I just needed to prioritize SSR over SSG, then things make more sense.

https://docs.astro.build/en/guides/server-side-rendering/#enabling-ssr-in-your-project

It seems like Astro could be in fact a more elegant replacement for Next.js or Gatsby, especially for content driven apps.

Time to implement: ~ 0.5h

Expand Down
17 changes: 14 additions & 3 deletions pokedex-app/astro-pokedex/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { defineConfig } from 'astro/config';

import { defineConfig } from "astro/config";
import tailwind from "@astrojs/tailwind";
import react from "@astrojs/react";

import node from "@astrojs/node";

// https://astro.build/config
export default defineConfig({
integrations: [tailwind()]
output: "server",
integrations: [tailwind(), react()],
vite: {
ssr: {
noExternal: ["bulma"]
}
},
adapter: node({
mode: "standalone"
})
});
7 changes: 7 additions & 0 deletions pokedex-app/astro-pokedex/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,15 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/node": "^6.0.1",
"@astrojs/react": "^3.0.2",
"@astrojs/tailwind": "^5.0.0",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"astro": "^3.1.2",
"bulma": "^0.9.4",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"tailwindcss": "^3.0.24"
}
}
7 changes: 0 additions & 7 deletions pokedex-app/astro-pokedex/src/components/Grid.astro

This file was deleted.

72 changes: 72 additions & 0 deletions pokedex-app/astro-pokedex/src/components/Grid.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useState, useEffect } from "react";
import ItemCard from "./ItemCard.jsx";

const FETCH_LIMIT = 30;

function getPostIdFromUrl(url) {
return url.split("/").reverse()[1];
}

function Grid({ items = [], startPage }) {
console.log("items", items);
const [data, setData] = useState(items);
const [displayData, setDisplayData] = useState(items);
const [searchValue, setSearchValue] = useState("");
const [page, setPage] = useState(startPage);

useEffect(() => {
// prevent run on first render
if (page !== startPage) {
const fetchData = async () => {
const response = await fetch(
`https://pokeapi.co/api/v2/pokemon?limit=${FETCH_LIMIT}&offset=${
page * FETCH_LIMIT
}`
);
const res = await response.json();
setData([...data, ...res.results]);
};

fetchData();
}
}, [page]);

useEffect(() => {
const filteredCollection = data.filter((item) =>
item.name.includes(searchValue)
);
setDisplayData(filteredCollection);
}, [data, searchValue]);

return (
<div className="grid-container">
<div className="grid-search">
<input
id="search-input"
className="input is-medium"
type="text"
placeholder="Search"
onChange={(e) => setSearchValue(e.target.value)}
/>
</div>
<div className="grid-wrapper">
{displayData.map((item) => {
const postId = getPostIdFromUrl(item.url);
return (
<a href={`/detail/${postId}`} key={postId}>
<ItemCard postId={postId} name={item.name} />
</a>
);
})}
</div>
<button
className="load-more button is-primary is-fullwidth"
onClick={() => setPage(page + 1)}
>
Load More
</button>
</div>
);
}

export default Grid;
40 changes: 0 additions & 40 deletions pokedex-app/astro-pokedex/src/components/ItemCard.astro

This file was deleted.

47 changes: 47 additions & 0 deletions pokedex-app/astro-pokedex/src/components/ItemCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
function DetailTable({ height, weight, types }) {
return (
height &&
weight &&
types && (
<table className="mb-4 mt-4">
<tbody>
<tr>
<th>Height:</th>
<td>{height}</td>
</tr>
<tr>
<th>Weight:</th>
<td>{weight}</td>
</tr>
<tr>
<th>Types:</th>
<td>{types.map((type) => type.type.name).join(', ')}</td>
</tr>
</tbody>
</table>
)
)
}

function ItemCard({ postId, name, height, weight, types }) {
return (
<div className="tile">
<figure className="image">
<img
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${postId}.png`}
alt={name}
/>
</figure>
<div className="content">
<h3>
<span className="prefix">#{postId}:&nbsp;</span>
{name}
</h3>
<DetailTable {...{ height, weight, types }} />
</div>
</div>
)
}

export default ItemCard

10 changes: 9 additions & 1 deletion pokedex-app/astro-pokedex/src/layouts/Layout.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
---
import "bulma/css/bulma.min.css";
import Navigation from "../components/Navigation.astro";
import Footer from "../components/Footer.astro";
interface Props {
title: string;
}
Expand All @@ -17,7 +21,11 @@ const { title } = Astro.props;
<title>{title}</title>
</head>
<body>
<slot />
<Navigation />
<main class="container">
<slot />
</main>
<Footer />
</body>
</html>
<style is:global>
Expand Down
19 changes: 19 additions & 0 deletions pokedex-app/astro-pokedex/src/pages/about.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
import Layout from "../layouts/Layout.astro";
export const prerender = true;
---

<Layout title="Pokedex App - About">
<div class="content about-page">
<h1>About</h1>
<p>
This app serves as an objective example for comparison of different web
frontend frameworks (Next.js, Nuxt, SvelteKit). By developing the same
(identical) app three times, one can estimate more closely the strenghts
and weaknesses of a certain framework.
</p>
</div>
</Layout>

<style></style>
13 changes: 13 additions & 0 deletions pokedex-app/astro-pokedex/src/pages/detail/[id].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
import ItemCard from "../../components/ItemCard.jsx";
import Layout from "../../layouts/Layout.astro";
const { id } = Astro.params;
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
const data = await response.json();
---

<Layout title={`Pokedex App - ${data.name}`}>
<a className="button is-ghost mb-4" href="/">&lsaquo;&nbsp;Back</a>
<ItemCard postId={data.id} {...data} />
</Layout>
19 changes: 13 additions & 6 deletions pokedex-app/astro-pokedex/src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
---
import Grid from "../components/Grid.jsx";
import Layout from "../layouts/Layout.astro";
export const prerender = true;
const FETCH_LIMIT = 30;
const response = await fetch(
`https://pokeapi.co/api/v2/pokemon?limit=${FETCH_LIMIT}
}`
);
const data = await response.json();
---

<Layout title="Welcome to Astro.">
<main>
<h1 class="text-3xl font-bold color-blue underline">Hello world!</h1>
</main>
<Layout title="Pokedex App">
<Grid items={data.results} startPage={0} client:load />
</Layout>

<style></style>
8 changes: 6 additions & 2 deletions pokedex-app/astro-pokedex/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"extends": "astro/tsconfigs/base"
}
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}
Loading

0 comments on commit ef67912

Please sign in to comment.