I’m working on an Astro project where I’m building a classic Pokédex. I have a PokemonCard.astro component that renders a Pokémon's details, and I’m trying to load more Pokémon dynamically when a "Load More" button is clicked. However, I’m currently creating the HTML elements manually in JavaScript, which feels redundant since I already have a PokemonCard component (I tried to reuse my component in the script but it doesnt work).
Is there a way to dynamically create and render Astro components (like PokemonCard) in the browser without manually creating the HTML elements? If not, what’s the best approach to achieve this?
Here’s the code:
---
import Layout from '../layouts/Layout.astro';
import PokemonCard from '../components/PokemonCard.astro';
import { getPokemons } from '../lib/controllers/pokemonController';
import type { PokemonSmall } from '../lib/models/pokemonModels';
const pokemons: PokemonSmall[] | undefined = await getPokemons(0, 12);
---
<Layout title="Pokedex">
<main class="m-auto">
<section id="pokemon-grid">
<div class="grid grid-cols-2 gap-7 p-2 mt-32
md:grid-cols-4">
{
pokemons?.map((pokemon : PokemonSmall) => (
<PokemonCard {pokemon}/>
))
}
</div>
</section>
<section class="flex justify-center items-center">
<button id="load-more-pkmn"
class="p-4 bg-slate-400/20 border-gray-500 border rounded-2xl my-4
transition-transform transform hover:scale-105">Cargar más pokémons</button>
</section>
</main>
</Layout>
<script>
import { getPokemons } from "../lib/controllers/pokemonController";
import { TypeColors, type PokemonSmall, type PokemonType } from "../lib/models/pokemonModels";
import { capitalizeFirstLetter, mapId } from "../lib/utils/utils";
let offset = 12;
const limit = 12;
const loadMorePkmn = document.getElementById('load-more-pkmn');
if(loadMorePkmn) {
loadMorePkmn.addEventListener('click', async () => {
const pokemons : PokemonSmall [] | undefined = await getPokemons(offset, limit);
offset += 12;
const pokemonGrid = document.getElementById('pokemon-grid');
const divPokemons = document.createElement('div');
divPokemons.className = 'grid grid-cols-2 gap-7 p-2 md:grid-cols-4';
pokemons?.map((pokemon : PokemonSmall) => {
console.log(pokemon)
const a = document.createElement('a');
a.className = 'w-60 h-60 p-1 flex flex-col items-center bg-slate-400/10 border-gray-500 border rounded-2xl hover:bg-gray-200 cursor-pointer';
const image = document.createElement('img');
image.className = 'w-28';
const h3 = document.createElement('h3');
h3.className = 'text-2xl font-bold tracking-wide mt-1';
const p = document.createElement('p');
p.className = 'text-xs tracking-wide p-1';
const divTypes = document.createElement('div');
divTypes.className = 'flex flex-row space-x-1 mt-2 p-1 gap-2';
a.href = `pokemon/${pokemon.id}`;
image.src = pokemon.image; image.alt = `Una foto de ${pokemon.name}`;
a.appendChild(image);
h3.innerText = capitalizeFirstLetter(pokemon.name);
a.appendChild(h3);
p.innerText = `No. ${mapId(pokemon.id)}`;
a.appendChild(p);
pokemon.types.map((types : PokemonType) => {
const pType = document.createElement('p');
pType.className = ` ${TypeColors[types.type.name]} opacity-80 rounded text-white text-center font-medium tracking-wide py-1 px-2`;
pType.innerText = types.type.name;
divTypes.appendChild(pType);
});
a.appendChild(divTypes);
divPokemons.appendChild(a);
});
pokemonGrid?.appendChild(divPokemons);
});
}
</script>
I thought about using a frontend framework like React or Vue, but I’d like to stick to Astro if possible.
I’m working on an Astro project where I’m building a classic Pokédex. I have a PokemonCard.astro component that renders a Pokémon's details, and I’m trying to load more Pokémon dynamically when a "Load More" button is clicked. However, I’m currently creating the HTML elements manually in JavaScript, which feels redundant since I already have a PokemonCard component (I tried to reuse my component in the script but it doesnt work).
Is there a way to dynamically create and render Astro components (like PokemonCard) in the browser without manually creating the HTML elements? If not, what’s the best approach to achieve this?
Here’s the code:
---
import Layout from '../layouts/Layout.astro';
import PokemonCard from '../components/PokemonCard.astro';
import { getPokemons } from '../lib/controllers/pokemonController';
import type { PokemonSmall } from '../lib/models/pokemonModels';
const pokemons: PokemonSmall[] | undefined = await getPokemons(0, 12);
---
<Layout title="Pokedex">
<main class="m-auto">
<section id="pokemon-grid">
<div class="grid grid-cols-2 gap-7 p-2 mt-32
md:grid-cols-4">
{
pokemons?.map((pokemon : PokemonSmall) => (
<PokemonCard {pokemon}/>
))
}
</div>
</section>
<section class="flex justify-center items-center">
<button id="load-more-pkmn"
class="p-4 bg-slate-400/20 border-gray-500 border rounded-2xl my-4
transition-transform transform hover:scale-105">Cargar más pokémons</button>
</section>
</main>
</Layout>
<script>
import { getPokemons } from "../lib/controllers/pokemonController";
import { TypeColors, type PokemonSmall, type PokemonType } from "../lib/models/pokemonModels";
import { capitalizeFirstLetter, mapId } from "../lib/utils/utils";
let offset = 12;
const limit = 12;
const loadMorePkmn = document.getElementById('load-more-pkmn');
if(loadMorePkmn) {
loadMorePkmn.addEventListener('click', async () => {
const pokemons : PokemonSmall [] | undefined = await getPokemons(offset, limit);
offset += 12;
const pokemonGrid = document.getElementById('pokemon-grid');
const divPokemons = document.createElement('div');
divPokemons.className = 'grid grid-cols-2 gap-7 p-2 md:grid-cols-4';
pokemons?.map((pokemon : PokemonSmall) => {
console.log(pokemon)
const a = document.createElement('a');
a.className = 'w-60 h-60 p-1 flex flex-col items-center bg-slate-400/10 border-gray-500 border rounded-2xl hover:bg-gray-200 cursor-pointer';
const image = document.createElement('img');
image.className = 'w-28';
const h3 = document.createElement('h3');
h3.className = 'text-2xl font-bold tracking-wide mt-1';
const p = document.createElement('p');
p.className = 'text-xs tracking-wide p-1';
const divTypes = document.createElement('div');
divTypes.className = 'flex flex-row space-x-1 mt-2 p-1 gap-2';
a.href = `pokemon/${pokemon.id}`;
image.src = pokemon.image; image.alt = `Una foto de ${pokemon.name}`;
a.appendChild(image);
h3.innerText = capitalizeFirstLetter(pokemon.name);
a.appendChild(h3);
p.innerText = `No. ${mapId(pokemon.id)}`;
a.appendChild(p);
pokemon.types.map((types : PokemonType) => {
const pType = document.createElement('p');
pType.className = ` ${TypeColors[types.type.name]} opacity-80 rounded text-white text-center font-medium tracking-wide py-1 px-2`;
pType.innerText = types.type.name;
divTypes.appendChild(pType);
});
a.appendChild(divTypes);
divPokemons.appendChild(a);
});
pokemonGrid?.appendChild(divPokemons);
});
}
</script>
I thought about using a frontend framework like React or Vue, but I’d like to stick to Astro if possible.
Share Improve this question asked Jan 29 at 23:18 emmyemmy 211 silver badge2 bronze badges1 Answer
Reset to default 1There is the HTML dialog element (in all modern browsers) and popover attribute (in Firefox, Chrome and Safari >= 17).
Or alternatively, you can create the HTML for your PokemonCard normally in the Astro component, but hide it with CSS. Then you just add a few lines of JavaScript to unhide. Assuming a function getPokemons(startIndex, endIndex)
:
import PokemonCard from '../components/PokemonCard.astro';
import { getPokemons } from '../lib/controllers/pokemonController';
---
{getPokemons(0, 10).map(pokemon =>
<PokemonCard pokemon={pokemon}/>}
<button id="showMoreBtn">Show more</button>
<div id="pokemon" style="display: hidden;">
{getPokemons(10, 20).map(pokemon =>
<PokemonCard pokemon={pokemon}/>}
</div>
<script>
document.getElementById('showMoreBtn')?.addEventListener('click', () => {
document.getElementById('pokemon').style.display = 'block';
})
</script>