Compare commits

...

23 Commits

Author SHA1 Message Date
88fb450c5a Fixed pokedexApi.test.ts 2023-04-12 23:24:29 +10:00
92f7111943 Fixed filterPokemonByType problem by creating separate function and write unit tests 2023-04-12 23:23:52 +10:00
fd21848a85 Implemented setTypeOptions and setSelectedType at correct place 2023-04-12 21:57:02 +10:00
e2bbe1d959 Implemented filteredPokemonList feature 2023-04-11 19:50:59 +10:00
968c6c5d95 Added setTypeOptions in useEffect() 2023-04-11 18:46:48 +10:00
1801e43192 Recreate useGetRegionOptions and useGetSortOptions again for getting region and sort options in Filter.tsx 2023-04-11 18:33:34 +10:00
73fa644a55 initialize Filter by setSelectedRegion and fetchPokemonsInTheRegion 2023-04-11 18:12:38 +10:00
790c7828b1 Remove startAppListening import in pokedexSlice.ts 2023-04-11 17:33:28 +10:00
183ce62f30 Changed pokedex state variable name; And remove unnecessary listener middleware 2023-04-11 17:31:48 +10:00
81fcac97c6 Implement fetchPokemonsInTheRegion to get all pokemons from the region 2023-04-10 15:52:57 +10:00
a09463a2b4 Implemented side effect of selectedRegion using app listener 2023-04-07 17:01:18 +10:00
83ae2f34d7 Change Redux code style by move all slice initialization process into pokedexSlice, Component are majorly for presentation 2023-04-06 23:57:05 +10:00
fa4fb04efb Remove non-necessary addAppListener (dynamic plugin) 2023-04-05 21:37:57 +10:00
40358e3900 Created utils functions within Pokedex 2023-04-05 21:37:03 +10:00
fed47e34b0 Fix importing from reduxjs/toolkit 2023-04-05 21:36:01 +10:00
63e3ce5fb6 Add setRegionPokemonIdsList in Filter 2023-04-05 21:34:53 +10:00
19c189c37d Setup typescripted listenerMiddleware 2023-04-04 23:12:22 +10:00
f30edc9700 Move types with pokedexSlice into types/slice.ts 2023-04-02 23:51:23 +10:00
40049ef7b5 Remove unnecessary setRegionPokemonList endpoint 2023-04-02 23:50:22 +10:00
89b5b976e1 Removed getRegionPokemonList endpoint and related tests 2023-04-02 21:07:53 +10:00
f4fd616b34 Steop using setRegionPokemonList, as now we link Region with Pokemon ID 2023-04-02 18:13:44 +10:00
5fee30437b Add TODO for removing unnecessary endpoints 2023-04-02 16:01:36 +10:00
a831e76275 Add correct yarn test:watchAll command in package.json, and remove unnecessary App.test.tsx 2023-04-02 15:49:35 +10:00
22 changed files with 21863 additions and 4352 deletions

View File

@ -18,6 +18,7 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test:watchAll": "react-scripts test --watchAll",
"eject": "react-scripts eject",
"prettier": "prettier \"src/**/*.{js,jsx,ts,tsx,css,scss,md}\" --write",
"format:check": "npm run prettier -- --check",

View File

@ -1,9 +0,0 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@ -0,0 +1,9 @@
import { createListenerMiddleware } from '@reduxjs/toolkit';
import type { TypedStartListening } from '@reduxjs/toolkit';
import { AppDispatch, RootState } from 'app/store';
export const listenerMiddleware = createListenerMiddleware();
export type AppStartListening = TypedStartListening<RootState, AppDispatch>;
export const startAppListening =
listenerMiddleware.startListening as AppStartListening;

View File

@ -1,4 +1,5 @@
import { configureStore } from '@reduxjs/toolkit';
import { listenerMiddleware } from './listenerMiddleware';
import { pokedexApi } from 'features/Pokedex/pokedexApi';
import { pokedexSlice } from 'features/Pokedex/pokedexSlice';
@ -11,7 +12,10 @@ export const store = configureStore({
[pokedexApi.reducerPath]: pokedexApi.reducer,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware().concat(pokedexApi.middleware),
getDefaultMiddleware().concat(
pokedexApi.middleware,
listenerMiddleware.middleware,
),
devTools: true,
});

View File

@ -0,0 +1,16 @@
import {
useGetRegionPokemons,
createRegionPokemonListOptionElements,
} from './Filters';
describe('Filters', () => {
describe('test utility functions', () => {
test('createOptionElements works correctly', () => {
const { data } = useGetRegionPokemons();
const optionElements = createRegionPokemonListOptionElements(data);
expect(optionElements[0].props.children).toBe('Kanto (1-151)');
expect(optionElements[1].props.children).toBe('Johto (152-251)');
expect(optionElements[2].props.children).toBe('Hoenn (252-386)');
});
});
});

View File

@ -1,71 +1,90 @@
import React, { useEffect } from 'react';
import {
useGetRegionListQuery,
useGetTypeListQuery,
useGetRegionPokemonListQuery,
} from 'features/Pokedex/pokedexApi';
import { useGetTypeListQuery } from 'features/Pokedex/pokedexApi';
import {
setSelectedRegion,
setSelectedType,
setSelectedSort,
setFetchingRegionPokemonList,
fetchPokemonsInTheRegion,
setRegionOptions,
setSortOptions,
setTypeOptions,
} from 'features/Pokedex/pokedexSlice';
import { RegionPokemonRange } from 'features/Pokedex/types/slice';
import { useAppDispatch, useAppSelector } from 'app/hooks';
export const createRegionPokemonListOptionElements = (
data: RegionPokemonRange[],
) => {
return data.map(({ region, startId, endId }) => {
const value = `${region}`;
const label = `${
region.charAt(0).toUpperCase() + region.slice(1)
} (${startId}-${endId})`;
return (
<option key={region} value={value}>
{label}
</option>
);
});
};
const useGetRegionOptions = () => {
const data: RegionPokemonRange[] = [
{ region: 'kanto', startId: 1, endId: 151 },
{ region: 'johto', startId: 152, endId: 251 },
{ region: 'hoenn', startId: 252, endId: 386 },
{ region: 'sinnoh', startId: 387, endId: 493 },
{ region: 'unova', startId: 494, endId: 649 },
{ region: 'kalos', startId: 650, endId: 721 },
{ region: 'alola', startId: 722, endId: 809 },
{ region: 'galar', startId: 810, endId: 898 },
];
return { data: data };
};
const useGetSortOptions = () => {
const sortOptions = [
const data = [
{ name: 'ID', value: 'id' },
{ name: 'Name', value: 'name' },
];
return { data: sortOptions };
return { data: data };
};
const Filters = () => {
const dispatch = useAppDispatch();
const handleRegionChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
dispatch(setSelectedRegion(event.target.value));
};
const handleTypeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
dispatch(setSelectedType(event.target.value));
};
const handleSortChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
dispatch(setSelectedSort(event.target.value));
};
const { data: regionsData, isLoading: regionsLoading } =
useGetRegionListQuery();
const { data: typesData, isLoading: typesLoading } = useGetTypeListQuery();
const { data: sortOptions } = useGetSortOptions();
// Send the first region as the default selected region
useEffect(() => {
if (regionsData && regionsData.results.length > 0) {
dispatch(setSelectedRegion(regionsData.results[0].name));
}
}, [regionsData, dispatch]);
// Send the first type as the default selected type
useEffect(() => {
if (typesData && typesData.results.length > 0) {
dispatch(setSelectedType(typesData.results[0].name));
}
}, [typesData, dispatch]);
const selectedRegion = useAppSelector(state => state.pokedex.selectedRegion);
const selectedType = useAppSelector(state => state.pokedex.selectedType);
const selectedSort = useAppSelector(state => state.pokedex.selectedSort);
const { refetch: refetchRegionPokemonList } = useGetRegionPokemonListQuery(
selectedRegion,
{ skip: !selectedRegion },
const regionPokemonList = useAppSelector(
state => state.pokedex.regionOptions,
);
const { data: fetchedRegionOptions } = useGetRegionOptions();
const { data: fetchedTypeOptions, isLoading: isFetchingTypeOptions } =
useGetTypeListQuery();
const { data: fetchedSortOptions } = useGetSortOptions();
useEffect(() => {
if (selectedRegion) {
dispatch(setFetchingRegionPokemonList(true));
refetchRegionPokemonList();
dispatch(setFetchingRegionPokemonList(false));
dispatch(setRegionOptions(fetchedRegionOptions));
dispatch(setSortOptions(fetchedSortOptions));
dispatch(setSelectedRegion(fetchedRegionOptions[0].region));
dispatch(fetchPokemonsInTheRegion(fetchedRegionOptions[0].region));
dispatch(setSelectedSort(fetchedSortOptions[0].value));
}, []);
useEffect(() => {
if (!isFetchingTypeOptions && fetchedTypeOptions) {
dispatch(setTypeOptions(fetchedTypeOptions.results));
dispatch(setSelectedType(fetchedTypeOptions.results[0].name));
}
}, [selectedRegion, refetchRegionPokemonList]);
}, [isFetchingTypeOptions]);
const optionElements =
createRegionPokemonListOptionElements(regionPokemonList);
return (
<>
@ -75,18 +94,13 @@ const Filters = () => {
<div>REGION</div>
<select
name="regionSelect"
disabled={regionsLoading}
onChange={handleRegionChange}
onChange={e => {
dispatch(setSelectedRegion(e.target.value));
dispatch(fetchPokemonsInTheRegion(e.target.value));
}}
value={selectedRegion}
>
{regionsLoading ? (
<option>Loading...</option>
) : (
regionsData?.results.map(region => (
<option key={region.name} value={region.name}>
{region.name}
</option>
))
)}
{optionElements}
</select>
</div>
</div>
@ -95,13 +109,13 @@ const Filters = () => {
<div>TYPE</div>
<select
name="regionSelect"
disabled={regionsLoading}
onChange={handleTypeChange}
onChange={e => dispatch(setSelectedType(e.target.value))}
value={selectedType}
>
{typesLoading ? (
{isFetchingTypeOptions ? (
<option>Loading...</option>
) : (
typesData?.results.map(type => (
fetchedTypeOptions?.results.map(type => (
<option key={type.name} value={type.name}>
{type.name}
</option>
@ -115,10 +129,11 @@ const Filters = () => {
<div>SORT BY</div>
<select
name="sortSelect"
disabled={typesLoading}
onChange={handleSortChange}
disabled={isFetchingTypeOptions}
onChange={e => dispatch(setSelectedSort(e.target.value))}
value={selectedSort}
>
{sortOptions.map(option => (
{fetchedSortOptions.map(option => (
<option key={option.value} value={option.value}>
{option.name}
</option>

View File

@ -3,25 +3,46 @@ import Pokemon from './Pokemon';
import Filters from './Filters';
import Loading from 'components/Loading';
import charizard from 'features/Pokedex/Pokemon/assets/stories/charizard.svg';
import { useAppSelector } from 'app/hooks';
import { PokemonResponseData } from './types/api';
export const filterPokemonByType = (
pokemonList: PokemonResponseData[],
selectedType: string,
) => {
return pokemonList.filter(
pokemon =>
selectedType === 'All Types' ||
pokemon.types.some(type => type.type.name === selectedType),
);
};
const Pokedex = () => {
const isFetchingRegionPokemonList = useAppSelector(
state => state.pokedex.fetchingRegionPokemonList,
const isLoadingPokemons = useAppSelector(
state => state.pokedex.isLoadingPokemons,
);
const selectedType = useAppSelector(state => state.pokedex.selectedType);
const selectedSort = useAppSelector(state => state.pokedex.selectedSort);
const pokemonList = useAppSelector(state => state.pokedex.pokemonList);
const filteredPokemonList = filterPokemonByType(pokemonList, selectedType);
return (
<>
<Filters />
{isFetchingRegionPokemonList ? (
{isLoadingPokemons ? (
<Loading />
) : (
<Pokemon
name={'Charizard'}
number={6}
image={charizard}
types={['fire', 'flying']}
/>
filteredPokemonList.map(pokemon => (
<Pokemon
key={pokemon.id}
name={pokemon.name}
number={pokemon.id}
image={pokemon.sprites.other.dream_world.front_default}
types={pokemon.types.map(type => type.type.name)}
/>
))
)}
</>
);

View File

@ -0,0 +1,44 @@
import { filterPokemonByType } from 'features/Pokedex/Pokedex';
import { PokemonResponseData } from 'features/Pokedex/types/api';
import pokemon1 from 'features/Pokedex/__test__/pokemon1.json';
import pokemon2 from 'features/Pokedex/__test__/pokemon2.json';
import { AppDispatch, AppStore } from 'app/store';
import { configureStore, Store } from '@reduxjs/toolkit';
import { pokedexSlice } from 'features/Pokedex/pokedexSlice';
import { pokedexApi } from 'features/Pokedex/pokedexApi';
import { listenerMiddleware } from 'app/listenerMiddleware';
let store: AppStore;
let dispatch: AppDispatch;
describe('filterPokemonByType works correctly', () => {
beforeEach(() => {
store = configureStore({
reducer: {
pokedex: pokedexSlice.reducer,
[pokedexApi.reducerPath]: pokedexApi.reducer,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware().concat(
pokedexApi.middleware,
listenerMiddleware.middleware,
),
});
});
const pokemonList: PokemonResponseData[] = [pokemon1, pokemon2];
it('should return all Pokemon if the selected type is "All Types"', () => {
const selectedType = 'All Types';
const filteredList = filterPokemonByType(pokemonList, selectedType);
expect(filteredList).toEqual(pokemonList);
});
it('should return only Pokemon of the selected type', () => {
const selectedType = 'fire';
const filteredList = filterPokemonByType(pokemonList, selectedType);
const allPokemonAreOfTypeFire = filteredList.every(pokemon =>
pokemon.types.some(type => type.type.name === selectedType),
);
expect(allPokemonAreOfTypeFire).toBe(true);
});
});

View File

@ -3,8 +3,9 @@ import { pokedexSlice } from 'features/Pokedex/pokedexSlice';
import { configureStore } from '@reduxjs/toolkit';
import region1 from 'features/Pokedex/__test__/responses/region1.json';
import pokemon1 from 'features/Pokedex/__test__/responses/pokemon1.json';
import { AppStore } from 'app/store';
import { RegionListResponseData, TypeListResponseData } from '../types/api';
import { AppStore } from 'app/store';
import { listenerMiddleware } from '../../../app/listenerMiddleware';
let store: AppStore;
describe('pokedexApi', () => {
@ -15,7 +16,10 @@ describe('pokedexApi', () => {
[pokedexApi.reducerPath]: pokedexApi.reducer,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware().concat(pokedexApi.middleware),
getDefaultMiddleware().concat(
pokedexApi.middleware,
listenerMiddleware.middleware,
),
});
});
@ -114,16 +118,5 @@ describe('pokedexApi', () => {
// @ts-ignore
expect(pokemonListData?.previous).toBeUndefined();
});
test('query getRegionPokemonList for johto should return correct data in list', async () => {
await store.dispatch(
pokedexApi.endpoints.getRegionPokemonList.initiate('johto'),
);
const pokemonListData = pokedexApi.endpoints.getRegionPokemonList.select(
'johto',
)(store.getState()).data;
expect(pokemonListData).toHaveLength(19);
}, 100000000000);
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +0,0 @@
{
"areas": [
{
"name": "blackthorn-city-area",
"url": "https://pokeapi.co/api/v2/location-area/249/"
}
],
"game_indices": [
{
"game_index": 136,
"generation": {
"name": "generation-iv",
"url": "https://pokeapi.co/api/v2/generation/4/"
}
}
],
"id": 65,
"name": "blackthorn-city",
"names": [
{
"language": {
"name": "fr",
"url": "https://pokeapi.co/api/v2/language/5/"
},
"name": "Ebènelle"
},
{
"language": {
"name": "en",
"url": "https://pokeapi.co/api/v2/language/9/"
},
"name": "Blackthorn City"
}
],
"region": { "name": "johto", "url": "https://pokeapi.co/api/v2/region/2/" }
}

View File

@ -1,40 +0,0 @@
{
"areas": [
{
"name": "burned-tower-1f",
"url": "https://pokeapi.co/api/v2/location-area/212/"
},
{
"name": "burned-tower-b1f",
"url": "https://pokeapi.co/api/v2/location-area/213/"
}
],
"game_indices": [
{
"game_index": 206,
"generation": {
"name": "generation-iv",
"url": "https://pokeapi.co/api/v2/generation/4/"
}
}
],
"id": 66,
"name": "burned-tower",
"names": [
{
"language": {
"name": "fr",
"url": "https://pokeapi.co/api/v2/language/5/"
},
"name": "Tour Cendrée"
},
{
"language": {
"name": "en",
"url": "https://pokeapi.co/api/v2/language/9/"
},
"name": "Burned Tower"
}
],
"region": { "name": "johto", "url": "https://pokeapi.co/api/v2/region/2/" }
}

View File

@ -1,74 +0,0 @@
{
"id": 2,
"locations": [
{
"name": "blackthorn-city",
"url": "https://pokeapi.co/api/v2/location/65/"
},
{ "name": "burned-tower", "url": "https://pokeapi.co/api/v2/location/66/" }
],
"main_generation": {
"name": "generation-ii",
"url": "https://pokeapi.co/api/v2/generation/2/"
},
"name": "johto",
"names": [
{
"language": {
"name": "ja-Hrkt",
"url": "https://pokeapi.co/api/v2/language/1/"
},
"name": "ジョウト地方"
},
{
"language": {
"name": "ko",
"url": "https://pokeapi.co/api/v2/language/3/"
},
"name": "성도지방"
},
{
"language": {
"name": "fr",
"url": "https://pokeapi.co/api/v2/language/5/"
},
"name": "Johto"
},
{
"language": {
"name": "de",
"url": "https://pokeapi.co/api/v2/language/6/"
},
"name": "Johto"
},
{
"language": {
"name": "it",
"url": "https://pokeapi.co/api/v2/language/8/"
},
"name": "Johto"
},
{
"language": {
"name": "en",
"url": "https://pokeapi.co/api/v2/language/9/"
},
"name": "Johto"
}
],
"pokedexes": [
{ "name": "original-johto", "url": "https://pokeapi.co/api/v2/pokedex/3/" },
{ "name": "updated-johto", "url": "https://pokeapi.co/api/v2/pokedex/7/" }
],
"version_groups": [
{
"name": "gold-silver",
"url": "https://pokeapi.co/api/v2/version-group/3/"
},
{ "name": "crystal", "url": "https://pokeapi.co/api/v2/version-group/4/" },
{
"name": "heartgold-soulsilver",
"url": "https://pokeapi.co/api/v2/version-group/10/"
}
]
}

View File

@ -3,7 +3,6 @@ import {
pokeApiAllPagesCustomBaseQuery,
pokeApiFullListFetchArgs,
} from './paginationBaseQuery';
import { setFetchingRegionPokemonList } from './pokedexSlice';
import {
AreaResponseData,
LocationResponseData,
@ -66,67 +65,6 @@ export const pokedexApi = createApi({
getArea: builder.query<AreaResponseData, number | string>({
query: IdOrName => ({ url: `location-area/${IdOrName}` }),
}),
getRegionPokemonList: builder.query<PokemonListItem[], number | string>({
async queryFn(regionIdOrName, api) {
api.dispatch(setFetchingRegionPokemonList(true));
// Get region data
const regionData: RegionResponseData = await api
.dispatch(pokedexApi.endpoints.getRegion.initiate(regionIdOrName))
.unwrap();
// Get location data
const locationDataList: LocationResponseData[] = await Promise.all(
regionData.locations.map(location =>
api
.dispatch(
pokedexApi.endpoints.getLocation.initiate(location.name),
)
.unwrap(),
),
);
// Get area datas
const areaDataList: AreaResponseData[] = await Promise.all(
locationDataList
.flatMap(locationData => locationData.areas)
.map(area =>
api
.dispatch(pokedexApi.endpoints.getArea.initiate(area.name))
.unwrap(),
),
);
// Collect unique Pokemon
const uniquePokemonList = new Set<nameUrlPair>();
areaDataList.forEach(areaData => {
areaData.pokemon_encounters.forEach(pokemon => {
uniquePokemonList.add(pokemon.pokemon);
});
});
// Get Pokemon data
const pokemonDataList: PokemonListItem[] = await Promise.all(
Array.from(uniquePokemonList).map(pokemon =>
api
.dispatch(pokedexApi.endpoints.getPokemon.initiate(pokemon.name))
.unwrap()
.then(pokemonData => {
return {
name: pokemonData.name,
id: pokemonData.id,
type: pokemonData.types.map(type => type.type.name),
image: pokemonData.sprites.other.dream_world.front_default,
};
}),
),
);
api.dispatch(setFetchingRegionPokemonList(false));
return { data: Array.from(pokemonDataList) };
},
}),
}),
});
@ -139,5 +77,4 @@ export const {
useGetTypeQuery,
useGetAreaQuery,
useGetLocationQuery,
useGetRegionPokemonListQuery,
} = pokedexApi;

View File

@ -1,33 +1,63 @@
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
import { PokemonProps } from './Pokemon';
import type { RootState } from 'app/store';
import { nameUrlPair } from './types/api';
import { createAsyncThunk, createSlice, Dispatch } from '@reduxjs/toolkit';
import type { Slice, PayloadAction } from '@reduxjs/toolkit';
interface PokedexState {
selectedRegion: string;
selectedType: string;
selectedSort: string;
pokemonList: PokemonProps[];
regionPokemonList: nameUrlPair[];
fetchingRegionPokemonList: boolean;
}
import { PokedexState, RegionPokemonRange } from 'features/Pokedex/types/slice';
import { getStartAndEndIdsForRegion } from './utils';
import { PokemonResponseData } from './types/api';
import { pokedexApi } from './pokedexApi';
import { RootState } from '../../app/store';
pokedexApi.endpoints.getTypeList.initiate(); // initialize type list fetching
// typesData will be used in Filters.tsx
export const fetchPokemonsInTheRegion = createAsyncThunk<
PokemonResponseData[],
string,
{ state: RootState }
>('pokedex/setSelectedRegion', async (region: string, thunkAPI) => {
const { dispatch, getState } = thunkAPI;
const regionOptions = getState().pokedex.regionOptions;
const { startId, endId } = getStartAndEndIdsForRegion(region, regionOptions);
const pokemonIds = Array.from(
{ length: endId - startId + 1 },
(_, i) => i + startId,
);
// use pokemonIds to fetch pokemon data using getPokemonQuery and store in state
const pokemonList = await Promise.all(
pokemonIds.map(
id =>
dispatch(pokedexApi.endpoints.getPokemon.initiate(id)) as Promise<{
data: PokemonResponseData;
}>,
),
);
const pokemonListData = pokemonList.map(
(pokemon: { data: PokemonResponseData }) => pokemon.data,
);
return pokemonListData;
});
const initialState: PokedexState = {
regionOptions: [],
typeOptions: [],
sortOptions: [],
selectedRegion: '',
selectedType: '',
selectedSort: '',
isLoadingPokemons: true,
pokemonList: [],
regionPokemonList: [],
fetchingRegionPokemonList: false,
};
export const pokedexSlice = createSlice({
export const pokedexSlice: Slice<PokedexState> = createSlice({
name: 'pokedex',
initialState,
reducers: {
setSelectedRegion: (state, action: PayloadAction<string>) => {
state.selectedRegion = action.payload;
// call fetchPokemonsInTheRegion
fetchPokemonsInTheRegion(action.payload);
},
setSelectedType: (state, action: PayloadAction<string>) => {
state.selectedType = action.payload;
@ -35,12 +65,42 @@ export const pokedexSlice = createSlice({
setSelectedSort: (state, action: PayloadAction<string>) => {
state.selectedSort = action.payload;
},
setFetchingRegionPokemonList: (state, action: PayloadAction<boolean>) => {
state.fetchingRegionPokemonList = action.payload;
setRegionOptions: (state, action: PayloadAction<RegionPokemonRange[]>) => {
state.regionOptions = action.payload;
},
setRegionPokemonList: (state, action: PayloadAction<nameUrlPair[]>) => {
state.regionPokemonList = action.payload;
setTypeOptions: (state, action: PayloadAction<string[]>) => {
state.typeOptions = action.payload;
},
setSortOptions: (
state,
action: PayloadAction<{ name: string; value: string }[]>,
) => {
state.sortOptions = action.payload;
},
setIsLoadingPokemons: (state, action: PayloadAction<boolean>) => {
state.isLoadingPokemons = action.payload;
},
setPokemonList: (state, action: PayloadAction<PokemonResponseData[]>) => {
state.pokemonList = action.payload;
},
},
extraReducers: builder => {
// add fetchPokemonsInTheRegion
builder.addCase(fetchPokemonsInTheRegion.fulfilled, (state, action) => {
state.isLoadingPokemons = false;
state.pokemonList = action.payload;
});
builder.addMatcher(
pokedexApi.endpoints.getTypeList.matchFulfilled,
(state, action) => {
if (action.payload && action.payload.results.length > 0) {
const regionListResults = action.payload.results;
state.typeOptions = regionListResults.map(region => region.name);
state.selectedType = action.payload.results[0].name;
}
},
);
},
});
@ -48,8 +108,10 @@ export const {
setSelectedRegion,
setSelectedType,
setSelectedSort,
setFetchingRegionPokemonList,
setRegionPokemonList,
setRegionOptions,
setTypeOptions,
setSortOptions,
setPokemonList,
} = pokedexSlice.actions;
export default pokedexSlice.reducer;

View File

@ -0,0 +1,18 @@
import { PokemonResponseData } from './api';
export type PokedexState = {
selectedRegion: string;
regionOptions: RegionPokemonRange[];
selectedType: string;
typeOptions: string[];
selectedSort: string;
sortOptions: { name: string; value: string }[];
isLoadingPokemons: boolean;
pokemonList: PokemonResponseData[];
};
export type RegionPokemonRange = {
region: string;
startId: number;
endId: number;
};

View File

@ -0,0 +1,11 @@
import { RegionPokemonRange } from './types/slice';
export const getStartAndEndIdsForRegion = (
region: string,
data: RegionPokemonRange[],
): { startId: number; endId: number } => {
const regionData = data.find(data => data.region === region);
return regionData
? { startId: regionData.startId, endId: regionData.endId }
: { startId: 0, endId: 0 };
};

View File

@ -5,12 +5,6 @@ import regionList from 'features/Pokedex/__test__/responses/regionList.json';
import typeList from 'features/Pokedex/__test__/responses/typeList.json';
import pokemonListPg1 from 'features/Pokedex/__test__/responses/pokemonListPage1.json';
import pokemonListPg2 from 'features/Pokedex/__test__/responses/pokemonListPage2.json';
import region_johto from 'features/Pokedex/__test__/responses/region_johto.json';
import location_blackthorn_city from 'features/Pokedex/__test__/responses/location_blackthorn-city.json';
import location_burned_tower from 'features/Pokedex/__test__/responses/location_burned-tower.json';
import area_blackthorn_city_area from 'features/Pokedex/__test__/responses/area_blackthorn-city-area.json';
import area_burned_tower_1f from 'features/Pokedex/__test__/responses/area_burned-tower-1f.json';
import area_burned_tower_b1f from 'features/Pokedex/__test__/responses/area_burned-tower-b1f.json';
export const handlers = [
// mock https://pokeapi.co/api/v2/region/1
@ -40,24 +34,4 @@ export const handlers = [
return res(ctx.json(pokemonListPg2));
}
}),
// getRegionPokemonList
rest.get('https://pokeapi.co/api/v2/region/johto', (req, res, ctx) => {
return res(ctx.json(region_johto));
}),
rest.get('https://pokeapi.co/api/v2/location/65', (req, res, ctx) => {
return res(ctx.json(location_blackthorn_city));
}),
rest.get('https://pokeapi.co/api/v2/location/66', (req, res, ctx) => {
return res(ctx.json(location_burned_tower));
}),
rest.get('https://pokeapi.co/api/v2/location-area/249', (req, res, ctx) => {
return res(ctx.json(area_blackthorn_city_area));
}),
rest.get('https://pokeapi.co/api/v2/location-area/212', (req, res, ctx) => {
return res(ctx.json(area_burned_tower_1f));
}),
rest.get('https://pokeapi.co/api/v2/location-area/213', (req, res, ctx) => {
return res(ctx.json(area_burned_tower_b1f));
}),
];