diff --git a/apps/react/src/store/anime/selectors.ts b/apps/react/src/store/anime/selectors.ts index 1263324..1170ab6 100644 --- a/apps/react/src/store/anime/selectors.ts +++ b/apps/react/src/store/anime/selectors.ts @@ -2,6 +2,8 @@ import { createSelector } from '@reduxjs/toolkit'; import { RootState } from '../store'; +import { animeAdapter } from './state'; + /** Select anime loading state. */ export const selectAnimeListLoading = createSelector( (state: RootState) => state.anime.isLoading, @@ -21,10 +23,7 @@ export const selectAnimeListError = createSelector( ); /** Select all anime from store. */ -export const selectAllAnime = createSelector( - (state: RootState) => state.anime.animeList.allIds.map(id => state.anime.animeList.byId[id]), - animeList => animeList, -); +export const selectAllAnime = animeAdapter.getSelectors(state => state.anime).selectAll; /** Select url to next page of anime list from store. */ export const selectNextPageUrl = createSelector( diff --git a/apps/react/src/store/anime/slice.ts b/apps/react/src/store/anime/slice.ts index 3eb3ec1..ee3676a 100644 --- a/apps/react/src/store/anime/slice.ts +++ b/apps/react/src/store/anime/slice.ts @@ -1,7 +1,8 @@ -import { createSlice } from '@reduxjs/toolkit'; +import { createSlice, EntityState } from '@reduxjs/toolkit'; +import { Anime } from '@js-camp/core/models/anime'; import { fetchList, fetchNewPage } from './dispatchers'; -import { initialState } from './state'; +import { animeAdapter, initialState } from './state'; /** Anime list slice. */ export const animeSlice = createSlice({ @@ -13,16 +14,9 @@ export const animeSlice = createSlice({ state.isLoading = true; }) .addCase(fetchList.fulfilled, (state, action) => { - state.animeList.byId = {}; - state.animeList.allIds = []; - - action.payload.results.forEach(item => { - state.animeList.byId[item.id] = item; - state.animeList.allIds.push(item.id); - }); - + const animeState = state as EntityState; + animeAdapter.setAll(animeState, action.payload.results); state.nextPage = action.payload.next; - state.previousPage = action.payload.previous; state.isLoading = false; }) .addCase(fetchList.rejected, (state, action) => { @@ -36,17 +30,10 @@ export const animeSlice = createSlice({ state.isAdditionalLoading = true; }) .addCase(fetchNewPage.fulfilled, (state, action) => { - const { byId, allIds } = state.animeList; - - action.payload.results.forEach(item => { - byId[item.id] = item; - if (!allIds.find(id => id === item.id)) { - allIds.push(item.id); - } - }); + const animeState = state as EntityState; + animeAdapter.addMany(animeState, action.payload.results); state.nextPage = action.payload.next; - state.previousPage = action.payload.previous; state.isAdditionalLoading = false; }) .addCase(fetchNewPage.rejected, (state, action) => { diff --git a/apps/react/src/store/anime/state.ts b/apps/react/src/store/anime/state.ts index fd3d731..c0617aa 100644 --- a/apps/react/src/store/anime/state.ts +++ b/apps/react/src/store/anime/state.ts @@ -1,16 +1,14 @@ import { Anime } from '@js-camp/core/models/anime'; +import { createEntityAdapter } from '@reduxjs/toolkit'; -import { NormalizedObjects } from '../store'; +/** Anime adapter. */ +export const animeAdapter = createEntityAdapter({ + selectId: (anime: Anime) => anime.id, +}); /** Anime list state. */ export type AnimeListState = { - /** Anime list. */ - readonly animeList: NormalizedObjects; - - /** Url to previous page of anime list. */ - readonly previousPage: string | null; - /** Url to next page of anime list. */ readonly nextPage: string | null; @@ -25,10 +23,10 @@ export type AnimeListState = { }; /** Initial state for anime list state. */ -export const initialState: AnimeListState = { - isLoading: false, - isAdditionalLoading: false, - animeList: { byId: {}, allIds: [] }, - previousPage: null, - nextPage: null, -}; +export const initialState = animeAdapter.getInitialState( + { + isLoading: false, + isAdditionalLoading: false, + nextPage: null, + }, +);