Tutorial
Dynamic routes

Dynamic routes

The homepage is ready with a full list of Pokémon. Good job!

Now, let's make the links point to an actual page by creating a new dynamic route.

Create the routes/pokemons folder and add the following files inside it:

  • [pokemon].rs
  • [pokemon].tsx

The new route will handle any request that points to a path in the /pokemons/SOME_POKEMON format. Example URL: http://localhost:3000/pokemons/bulbasaur.

Implementing a server-side route

Start with the server-side file by pasting the following content into [pokemon].rs:

// src/routes/pokemons/[pokemon].rs
use serde::{Deserialize, Serialize};
use reqwest::Client;
use tuono_lib::{Props, Request, Response};

const POKEMON_API: &str = "https://pokeapi.co/api/v2/pokemon";

#[derive(Debug, Serialize, Deserialize)]
struct Pokemon {
    name: String,
    id: u16,
    weight: u16,
    height: u16,
}

#[tuono_lib::handler]
async fn get_pokemon(req: Request, fetch: Client) -> Response {
    // The param `pokemon` is defined in the route filename [pokemon].rs
    let pokemon = req.params.get("pokemon").unwrap();

    return match fetch.get(format!("{POKEMON_API}/{pokemon}")).send().await {
        Ok(res) => {
            let data = res.json::<Pokemon>().await.unwrap();
            Response::Props(Props::new(data))
        }
        Err(_err) => Response::Props(Props::new("{}"))
    };
}

Implementing a client-side route

To set up the route client-side, simply paste the following code snippet into the [pokemon].tsx file.

// src/routes/pokemons/[pokemon].tsx
import type { JSX } from 'react'
import type { TuonoProps } from 'tuono'
import { Link } from 'tuono'

import PokemonView from '../../components/PokemonView'

interface Pokemon {
  id: number
  name: string
  weight: number
  height: number
}

export default function PokemonPage({
  isLoading,
  data,
}: TuonoProps<Pokemon>): JSX.Element {
  return (
    <div>
      <Link href="/">Back</Link>

      {isLoading && (
        <>
          <div>Loading...</div>
        </>
      )}

      {data?.id && <PokemonView pokemon={data} />}
    </div>
  )
}

You should now get an error saying that the PokemonView component does not exist. Let’s create it!

// src/components/PokemonView.tsx
import type { JSX } from 'react'

import styles from './PokemonView.module.css'

interface Pokemon {
  id: number
  name: string
  weight: number
  height: number
}

interface PokemonViewProps {
  pokemon: Pokemon
}

export default function PokemonView({
  pokemon,
}: PokemonViewProps): JSX.Element {
  return (
    <div className={styles.pokemon}>
      <div>
        <h1 className={styles.name}>{pokemon.name}</h1>
        <dl className={styles.spec}>
          <dt className={styles.label}>Weight: </dt>
          <dd>{pokemon.weight}lbs</dd>
        </dl>
        <dl className={styles.spec}>
          <dt className={styles.label}>Height: </dt>
          <dd>{pokemon.height}ft</dd>
        </dl>
      </div>
      <img
        src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${pokemon.id}.png`}
        alt=""
      />
    </div>
  )
}

Finally, let's add a stylesheet to complete the look:

/* src/components/PokemonView.module.css */
.pokemon {
  display: flex;
  justify-content: space-between;
  margin-top: 20px;
  border-radius: 18px;
  box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
  padding: 16px 32px;
  background: white;
}

.name {
  font-size: 50px;
  font-weight: 700;
}

.pokemon img {
  width: 250px;
}

.spec {
  display: flex;
  font-size: 18px;
  margin-top: 10px;

  > dt {
    margin-right: 6px;
  }
}

.label {
  font-weight: 700;
}
Edit page