
Hoy aprenderemos cómo crear una aplicación de trivia de tenis usando Next.js y Netlify. Esta pila de tecnología se ha convertido en mi opción en muchos proyectos. Permite un desarrollo rápido y una fácil implementación.
Sin más preámbulos, ¡saltemos!
lo que estamos usando
- Siguiente.js
- netlizar
- Mecanografiado
- CSS viento de cola
Por qué Next.js y Netlify
Puede pensar que esta es una aplicación simple que podría no requerir un marco React. La verdad es que Next.js me brinda un montón de funciones listas para usar que me permiten comenzar a codificar la parte principal de mi aplicación. Cosas como la configuración del paquete web, getServerSideProps
y la creación automática de funciones sin servidor de Netlify son algunos ejemplos.
Netlify también hace que la implementación de un repositorio git de Next.js sea muy fácil. Más sobre la implementación un poco más adelante.
lo que estamos construyendo
Básicamente, vamos a construir un juego de trivia que te muestra aleatoriamente el nombre de un jugador de tenis y tienes que adivinar de qué país es. Consta de cinco rondas y mantiene un puntaje continuo de cuántas aciertas.
Los datos que necesitamos para esta aplicación son una lista de jugadores junto con su país. Inicialmente, estaba pensando en consultar alguna API en vivo, pero pensándolo bien, decidí usar solo un archivo JSON local. Tomé una instantánea de RapidAPI y la incluí en el repositorio inicial.
El producto final se parece a esto:
Puede encontrar la versión final implementada en Netlify.
Recorrido de repositorio de inicio
Si desea seguir adelante, puede clonar este repositorio y luego ir a la start
rama:
git clone [email protected]:brenelz/tennis-trivia.git
cd tennis-trivia
git checkout start
En este repositorio de inicio, seguí adelante y escribí algunos repetitivos para poner las cosas en marcha. Creé una aplicación Next.js usando el comando npx create-next-app tennis-trivia
. Luego procedí a cambiar manualmente un par de archivos JavaScript a .ts
y .tsx
. Sorprendentemente, Next.js detectó automáticamente que quería usar TypeScript. ¡Era demasiado fácil! También seguí adelante y configuré Tailwind CSS utilizando este artículo como guía.
Basta de hablar, ¡vamos a codificar!
Configuración inicial
El primer paso es configurar las variables de entorno. Para el desarrollo local, hacemos esto con un .env.local
Archivo. Puedes copiar el .env.sample
del repositorio inicial.
cp .env.sample .env.local
Observe que actualmente tiene un valor, que es la ruta de nuestra aplicación. Usaremos esto en la parte frontal de nuestra aplicación, por lo que debemos prefijarlo con NEXT_PUBLIC_
.
Finalmente, usemos los siguientes comandos para instalar las dependencias e iniciar el servidor de desarrollo:
npm install
npm run dev
Ahora accedemos a nuestra aplicación en http://localhost:3000
. Deberíamos ver una página bastante vacía con solo un título:
Crear el marcado de la interfaz de usuario
En pages/index.tsx
, agreguemos el siguiente marcado al existente Home()
función:
export default function Home() {
return (
<div className="bg-blue-500">
<div className="max-w-2xl mx-auto text-center py-16 px-4 sm:py-20 sm:px-6 lg:px-8">
<h2 className="text-3xl font-extrabold text-white sm:text-4xl">
<span className="block">Tennis Trivia - Next.js Netlify</span>
</h2>
<div>
<p className="mt-4 text-lg leading-6 text-blue-200">
What country is the following tennis player from?
</p>
<h2 className="text-lg font-extrabold text-white my-5">
Roger Federer
</h2>
<form>
<input
list="countries"
type="text"
className="p-2 outline-none"
placeholder="Choose Country"
/>
<datalist id="countries">
<option>Switzerland</option>
</datalist>
<p>
<button
className="mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto"
type="submit"
>
Guess
</button>
</p>
</form>
<p className="mt-4 text-lg leading-6 text-white">
<strong>Current score:</strong> 0
</p>
</div>
</div>
</div>
);
Esto forma el andamio para nuestra interfaz de usuario. Como puede ver, estamos usando muchas clases de utilidad de Tailwind CSS para que las cosas se vean un poco más bonitas. También tenemos una entrada de autocompletar simple y un botón de envío. Aquí es donde seleccionarás el país del que crees que es el jugador y luego presionarás el botón. Por último, en la parte inferior, hay una puntuación que cambia según las respuestas correctas o incorrectas.
Configurando nuestros datos
Si echas un vistazo a la data
carpeta, debe haber una tennisPlayers.json
con todos los datos que necesitaremos para esta aplicación. Crear un lib
carpeta en la raíz y, dentro de ella, crear una players.ts
Archivo. Recuerda el .ts
Se requiere la extensión ya que es un archivo TypeScript. Definamos un tipo que coincida con nuestros datos JSON.
export type Player = {
id: number,
first_name: string,
last_name: string,
full_name: string,
country: string,
ranking: number,
movement: string,
ranking_points: number,
};
Así es como creamos un tipo en TypeScript. Tenemos el nombre de la propiedad a la izquierda, y el tipo que es a la derecha. Pueden ser tipos básicos, o incluso otros tipos.
A partir de aquí, creemos variables específicas que representen nuestros datos:
export const playerData: Player[] = require("../data/tennisPlayers.json");
export const top100Players = playerData.slice(0, 100);
const allCountries = playerData.map((player) => player.country).sort();
export const uniqueCountries = [...Array.from(new Set(allCountries))];
Un par de cosas a tener en cuenta es que estamos diciendo nuestro playerData
es una matriz de Player
tipos Esto se denota por los dos puntos seguidos por el tipo. De hecho, si pasamos el cursor sobre el playerData
podemos ver su tipo:
En esa última línea, obtenemos una lista única de países para usar en nuestro menú desplegable de países. Pasamos nuestros países a un conjunto de JavaScript, que elimina los valores duplicados. Luego creamos una matriz a partir de ella y la distribuimos en una nueva matriz. Puede parecer innecesario, pero esto se hizo para hacer feliz a TypeScript.
Lo crea o no, ¡esos son realmente todos los datos que necesitamos para nuestra aplicación!
¡Hagamos que nuestra interfaz de usuario sea dinámica!
Todos nuestros valores están codificados actualmente, pero cambiemos eso. Las piezas dinámicas son el nombre del tenista, la lista de países y la puntuación.
De nuevo en pages/index.tsx
, vamos a modificar nuestro getServerSideProps
función para crear una lista de cinco jugadores aleatorios, así como extraer nuestro uniqueCountries
variable.
import { Player, uniqueCountries, top100Players } from "../lib/players";
...
export async function getServerSideProps() {
const randomizedPlayers = top100Players.sort((a, b) => 0.5 - Math.random());
const players = randomizedPlayers.slice(0, 5);
return {
props: {
players,
countries: uniqueCountries,
},
};
}
Lo que sea que esté en el props
El objeto que devolvemos se pasará a nuestro componente React. Vamos a usarlos en nuestra página:
type HomeProps = {
players: Player[];
countries: string[];
};
export default function Home({ players, countries }: HomeProps) {
const player = players[0];
...
}
Como puede ver, definimos otro tipo para nuestro componente de página. Luego agregamos el HomeProps
escriba a la Home()
función. Hemos vuelto a especificar que players
es una matriz de Player
escribe.
Ahora podemos usar estos accesorios más abajo en nuestra interfaz de usuario. Reemplace “Roger Federer” con {player.full_name}
(es mi tenista favorito por cierto). Debería obtener un buen autocompletado en la variable del jugador, ya que enumera todos los nombres de propiedad a los que tenemos acceso debido a los tipos que definimos.
Más abajo de esto, ahora actualicemos la lista de países a esto:
<datalist id="countries">
{countries.map((country, i) => (
<option key={i}>{country}</option>
))}
</datalist>
Ahora que tenemos dos de las tres piezas dinámicas en su lugar, debemos abordar la partitura. Específicamente, necesitamos crear un estado para el puntaje actual.
export default function Home({ players, countries }: HomeProps) {
const [score, setScore] = useState(0);
...
}
Una vez hecho esto, reemplace el 0 con {score}
en nuestra interfaz de usuario.
Ahora puede comprobar nuestro progreso yendo a http://localhost:3000
. Puede ver que cada vez que se actualiza la página, obtenemos un nuevo nombre; y al escribir en el campo de entrada, enumera todos los países únicos disponibles.
Agregar algo de interactividad
Hemos recorrido un camino decente, pero necesitamos agregar algo de interactividad.
Conectando el botón de adivinar
Para esto, necesitamos tener alguna forma de saber qué país fue elegido. Hacemos esto agregando un poco más de estado y adjuntándolo a nuestro campo de entrada.
export default function Home({ players, countries }: HomeProps) {
const [score, setScore] = useState(0);
const [pickedCountry, setPickedCountry] = useState("");
...
return (
...
<input
list="countries"
type="text"
value={pickedCountry}
onChange={(e) => setPickedCountry(e.target.value)}
className="p-2 outline-none"
placeholder="Choose Country"
/>
...
);
}
A continuación, agreguemos un guessCountry
y adjúntelo al envío del formulario:
const guessCountry = () => {
if (player.country.toLowerCase() === pickedCountry.toLowerCase()) {
setScore(score + 1);
} else {
alert(‘incorrect’);
}
};
...
<form
onSubmit={(e) => {
e.preventDefault();
guessCountry();
}}
>
Todo lo que hacemos es básicamente comparar el país del jugador actual con el país adivinado. Ahora, cuando volvemos a la aplicación y acertamos el país, la puntuación aumenta como se esperaba.
Adición de un indicador de estado
Para hacer esto un poco más agradable, podemos renderizar alguna interfaz de usuario dependiendo de si la conjetura es correcta o no.
Entonces, creemos otra parte del estado para el estado y actualicemos el método de adivinación del país:
const [status, setStatus] = useState(null);
...
const guessCountry = () => {
if (player.country.toLowerCase() === pickedCountry.toLowerCase()) {
setStatus({ status: "correct", country: player.country });
setScore(score + 1);
} else {
setStatus({ status: "incorrect", country: player.country });
}
};
Luego renderice esta interfaz de usuario debajo del nombre del jugador:
{status && (
<div className="mt-4 text-lg leading-6 text-white">
<p>
You are {status.status}. It is {status.country}
</p>
<p>
<button
autoFocus
className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto"
>
Next Player
</button>
</p>
</div>
)}
Por último, queremos asegurarnos de que nuestro campo de entrada no se muestre cuando estamos en un estado correcto o incorrecto. Logramos esto envolviendo el formulario con lo siguiente:
{!status && (
<form>
...
</form>
)}
Ahora, si volvemos a la aplicación y adivinamos el país del jugador, obtenemos un bonito mensaje con el resultado de la adivinanza.
Progresando a través de los jugadores
Ahora probablemente viene la parte más desafiante: ¿Cómo pasamos de un jugador a otro?
Lo primero que tenemos que hacer es guardar el currentStep
en el estado para que podamos actualizarlo con un número del 0 al 4. Luego, cuando llega al 5, queremos mostrar un estado completo ya que el juego de trivia ha terminado.
Una vez más, agreguemos las siguientes variables de estado:
const [currentStep, setCurrentStep] = useState(0);
const [playersData, setPlayersData] = useState(players);
…entonces reemplace nuestra variable de jugador anterior con:
const player = playersData[currentStep];
A continuación, creamos un nextStep
función y conéctelo a la interfaz de usuario:
const nextStep = () => {
setPickedCountry("");
setCurrentStep(currentStep + 1);
setStatus(null);
};
...
<button
autoFocus
onClick={nextStep}
className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto"
>
Next Player
</button>
Ahora, cuando hacemos una conjetura y presionamos el botón de paso siguiente, nos lleva a un nuevo jugador de tenis. Vuelve a adivinar y vemos el siguiente, y así sucesivamente.
¿Qué sucede cuando le damos al siguiente golpe al último jugador? En este momento, obtenemos un error. Arreglemos eso agregando un condicional que represente que el juego ha sido completado. Esto sucede cuando la variable del jugador no está definida.
{player ? (
<div>
<p className="mt-4 text-lg leading-6 text-blue-200">
What country is the following tennis player from?
</p>
...
<p className="mt-4 text-lg leading-6 text-white">
<strong>Current score:</strong> {score}
</p>
</div>
) : (
<div>
<button
autoFocus
className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50 sm:w-auto"
>
Play Again
</button>
</div>
)}
Ahora vemos un buen estado completo al final del juego.
Botón reproducir de nuevo
¡Casi terminamos! Para nuestro botón “Jugar de nuevo”, queremos restablecer el estado de todo el juego. También queremos obtener una nueva lista de jugadores del servidor sin necesidad de actualizar. Lo hacemos así:
const playAgain = async () => {
setPickedCountry("");
setPlayersData([]);
const response = await fetch(
process.env.NEXT_PUBLIC_API_URL + "/api/newGame"
);
const data = await response.json();
setPlayersData(data.players);
setCurrentStep(0);
setScore(0);
};
<button
autoFocus
onClick={playAgain}
className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50 sm:w-auto"
>
Play Again
</button>
Observe que estamos usando la variable de entorno que configuramos antes a través del process.env
objeto. También estamos actualizando nuestro playersData
anulando el estado de nuestro servidor con el estado de nuestro cliente que acabamos de recuperar.
No hemos llenado nuestro newGame
ruta todavía, pero esto es fácil con las funciones sin servidor de Next.js y Netlify. Sólo necesitamos editar el archivo en pages/api/newGame.ts
.
import { NextApiRequest, NextApiResponse } from "next"
import { top100Players } from "../../lib/players";
export default (req: NextApiRequest, res: NextApiResponse) => {
const randomizedPlayers = top100Players.sort((a, b) => 0.5 - Math.random());
const top5Players = randomizedPlayers.slice(0, 5);
res.status(200).json({players: top5Players});
}
Esto se parece mucho a nuestro getServerSideProps
porque podemos reutilizar nuestras agradables variables auxiliares.
Si volvemos a la aplicación, observe que el botón “Reproducir de nuevo” funciona como se esperaba.
Mejorar los estados de enfoque
Una última cosa que podemos hacer para mejorar nuestra experiencia de usuario es establecer el foco en el campo de entrada del país cada vez que cambie el paso. Eso es solo un toque agradable y conveniente para el usuario. Esto lo hacemos usando un ref
y un useEffect
:
const inputRef = useRef(null);
...
useEffect(() => {
inputRef?.current?.focus();
}, [currentStep]);
<input
list="countries"
type="text"
value={pickedCountry}
onChange={(e) => setPickedCountry(e.target.value)}
ref={inputRef}
className="p-2 outline-none"
placeholder="Choose Country"
/>
Ahora podemos navegar mucho más fácilmente usando la tecla Enter y escribiendo un país.
Implementación en Netlify
Puede que se pregunte cómo desplegamos esta cosa. Bueno, usar Netlify lo hace tan simple ya que detecta una aplicación Next.js lista para usar y la configura automáticamente.
Todo lo que hice fue configurar un repositorio de GitHub y conectar mi cuenta de GitHub a mi cuenta de Netlify. A partir de ahí, simplemente elijo un repositorio para implementar y uso todos los valores predeterminados.
Lo único a tener en cuenta es que tienes que añadir el NEXT_PUBLIC_API_URL
variable de entorno y vuelva a implementar para que tenga efecto.
Puede encontrar mi versión final implementada aquí.
También tenga en cuenta que puede presionar el botón “Implementar en Netlify” en el repositorio de GitHub.
Conclusión
¡Uuuu, lo lograste! Ese fue un viaje y espero que hayas aprendido algo sobre React, Next.js y Netlify en el camino.
Tengo planes de expandir esta aplicación de trivia de tenis para usar Supabase en un futuro cercano, ¡así que estad atentos!
Si tiene alguna pregunta/comentario, no dude en comunicarse conmigo en Gorjeo.