Acho que aqui tem uma confusão entre duas perguntas diferentes que o código responde.
"Como é um card de evento?" — isso é responsabilidade de um componente. E ele existe: o map não está criando HTML solto, está renderizando um componente pra cada item. Cada card continua sendo componente, com sua estrutura e estilo encapsulados.
"Quantos cards existem e com quais dados?" — isso não é responsabilidade do card. O card não sabe (e não deveria saber) se vai haver um, cinco ou cinquenta iguais a ele. Quem sabe disso é quem tem a lista de eventos em mãos. E é exatamente isso que o map faz: pega um array de dados e devolve um array de componentes, um pra cada item.
{eventos.map(evento => (
<CardEvento key={evento.id} evento={evento} />
))}
Lê assim: "pra cada evento na lista, me dá um CardEvento com esse evento". O componente descreve um card. O map descreve a repetição. São coisas diferentes e é bom que fiquem separadas — se amanhã os eventos vierem de uma API em vez de um array fixo, o CardEvento não muda uma linha.
Por isso entender map importa tanto: no React, "renderizar uma lista" é "transformar um array de dados num array de elementos". Não existe for no JSX. Se map ainda parece estranho, vale praticar com JS puro antes — transformar [1, 2, 3] em [2, 4, 6], transformar uma lista de nomes em uma lista de <li>.
Sobre "haveria uma maneira diferente de codar": sim, e ela é bem comum. Se o map dentro do App.jsx incomoda, dá pra extrair a lista pra um componente próprio:
function ListaDeEventos({ eventos }) {
return (
<section>
{eventos.map(evento => (
<CardEvento key={evento.id} evento={evento} />
))}
</section>
)
}
E o App fica só com <ListaDeEventos eventos={eventos} />. Repara que o map não sumiu — ele só mudou de lugar. Ele continua sendo a única forma de dizer "um componente por item".
A diferença é onde você decide que essa responsabilidade mora.