Skip to main content

Page with dynamic parameters

In this tutorial, we will create a page with detailed information about the pokemon, available at the url /pokemon/:name. The pokeapi gives us information about the pokemon's elements, and its basic parameters, and we will try to display this information. To create a url with dynamic parameters, the dynamic part of the path to the page component must be in square brackets, e.g. for the url /pokemon/:name you have to create a component at the path routes/pokemon/[name]/index.tsx.

⌛ Create empty page component:

routes/pokemon/[name]/index.tsx
import React from 'react';

export const PokemonView = () => {
return <>Hi! This is PokemonView component :)</>;
};

export default PokemonView;

After that, you can click on any of the pokemon on the homepage http://localhost:3000/ and after going to http://localhost:3000/pokemon/bulbasaur/ you will see the PokemonView component.

It's time to download more pokemon information! The service PAGE_SERVICE_TOKEN will be used to get the dynamic parameters of the current route.

⌛ Add a new action to the pokemon entity to load data using dynamic parameters from the route:

entities/pokemon/model.ts
import { PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';

export const fetchPokemonAction = declareAction({
name: 'fetchPokemon',
async fn() {
// access to the `:name` parameter of the current route via PAGE_SERVICE_TOKEN
const { name } = this.deps.pageService.getCurrentRoute().params;

// loading information about the pokemon
const pokemonResponse = await this.deps.pokeapiHttpClient.get<Pokemon>(`/pokemon/${name}`);

// save information about the pokemon in the store
this.dispatch(pokemonLoadedEvent(pokemonResponse.payload));
},
deps: {
pokeapiHttpClient: POKEAPI_HTTP_CLIENT,
pageService: PAGE_SERVICE_TOKEN,
},
conditions: {
// disable caching of the action, since it must be executed again for different name values
dynamic: true,
},
});
tip

You can read more about the need to add an dynamic condition to an action that depends on page parameters in Action documentation

⌛ Connect action to a page:

pages/pokemon/index.tsx
import React from 'react';
import { fetchPokemonAction } from '~entities/pokemon';

export const PokemonView = () => {
return <>Hi! This is PokemonView component :)</>;
};

PokemonView.actions = [fetchPokemonAction];

export default PokemonView;

Before render Pokemon data, it is worth extending our Pokemon interface.

⌛ Complete the Pokemon interface:

entities/pokemon/model.ts
export type Pokemon = {
id: number;
name: string;
stats: PokemonStat[];
types: PokemonType[];
};

export type PokemonStat = {
// characteristic value
base_stat: number;
// characteristic name
stat: { name: string };
};

export type PokemonType = {
// element type
type: { name: string };
};

To get PAGE_SERVICE_TOKEN from the DI in the component, we'll use the useDi hook.

⌛ Add code to get information about the pokemon in PokemonView:

pages/pokemon/index.tsx
import React from 'react';
import { useStoreSelector } from '@tramvai/state';
import { useRoute } from '@tinkoff/router';
import { fetchPokemonAction } from '~entities/pokemon';

export const PokemonView = () => {
// access to the `:name` parameter of the current route via `useRoute` hook
const { name } = useRoute().params;
// get information about a specific pokemon
const pokemon = useStoreSelector(PokemonsStore, (pokemons) => pokemons[name]);

return <>Hi! This is PokemonView component :)</>;
}

PokemonView.actions = [fetchPokemonAction];

export default PokemonView;

⌛ And render all the data on the page:

pages/pokemon/index.tsx
import React from 'react';
import { useStoreSelector } from '@tramvai/state';
import { Link } from '@tramvai/module-router';
import { useRoute } from '@tinkoff/router';
import type { Pokemon, PokemonStat } from '~entities/pokemon';
import { fetchPokemonAction, PokemonsStore } from '~entities/pokemon';

// utility to search for characteristics, will allow us to draw only some
const findStatByName = (
pokemon: Pokemon,
statKey: string
): PokemonStat | undefined => {
return pokemon.stats.find((stat) => statKey === stat.stat.name);
};

export const PokemonView = () => {
const { name } = useRoute().params;
const pokemon = useStoreSelector(PokemonsStore, (pokemons) => pokemons[name]);

// If there is no information about the pokemon, consider it to be loading
if (!pokemon) {
return <div>Loading...</div>;
}

const hpStat = findStatByName(pokemon, 'hp');
const attackStat = findStatByName(pokemon, 'attack');
const defenseStat = findStatByName(pokemon, 'defense');
const speedStat = findStatByName(pokemon, 'speed');

return (
<div>
<div>
<Link url="/">Return to list</Link>
</div>
<img
alt={pokemon.name}
width={200}
height={200}
src={`https://img.pokemondb.net/artwork/large/${pokemon.name}.jpg`}
/>
<h2>{pokemon.name}</h2>
<div>
<p>Stats</p>
<ul>
{hpStat && <li>HP: {hpStat.base_stat}</li>}
{attackStat && <li>Attack: {attackStat.base_stat}</li>}
{defenseStat && <li>Defense: {defenseStat.base_stat}</li>}
{speedStat && <li>Speed: {speedStat.base_stat}</li>}
</ul>
</div>
<div>
<p>Types</p>
<ul>
{pokemon.types.map((type) => {
const typeKey = type.type.name;
return (
<li key={typeKey} data-type={typeKey}> {typeKey} </li>
);
})}
</ul>
</div>
</div>
);
}

PokemonView.actions = [fetchPokemonAction];

export default PokemonView;

Done!

Now you can go to the http://localhost:3000/pokemon/bulbasaur/ page, where you will find detailed information about this wonderful creature :)

Next lesson