From c5cc95c6864cfb1b88eaf1464b9559734dc8f8fc Mon Sep 17 00:00:00 2001 From: robertsavian Date: Sat, 23 Jan 2021 08:20:17 -0600 Subject: [PATCH 1/4] Convert Stores to use Classes --- .../pages/episodes-page/EpisodesPage.store.ts | 81 +++++++-------- .../pages/index-page/IndexPage.store.ts | 99 ++++++++++--------- src/pages/episodes/[episode_id].tsx | 2 +- src/pages/index.tsx | 2 +- src/stores/GlobalStore.ts | 4 +- src/stores/auth/AuthGlobalStore.ts | 72 +++++++------- src/stores/toast/ToastGlobalStore.ts | 43 ++++---- 7 files changed, 156 insertions(+), 147 deletions(-) diff --git a/src/components/pages/episodes-page/EpisodesPage.store.ts b/src/components/pages/episodes-page/EpisodesPage.store.ts index 58c2cca..74ca060 100644 --- a/src/components/pages/episodes-page/EpisodesPage.store.ts +++ b/src/components/pages/episodes-page/EpisodesPage.store.ts @@ -1,4 +1,4 @@ -import { observable } from 'mobx'; +import { makeAutoObservable } from 'mobx'; import groupBy from 'lodash.groupby'; import orderBy from 'lodash.orderby'; import { IEpisode, IEpisodeTable } from '../../../domains/shows/shows.types'; @@ -7,41 +7,44 @@ import { ApiResponse } from '../../../utils/http/http.types'; import { getGlobalStore } from '../../shared/global-store-provider/GlobalStoreProvider'; import { EpisodesToggleOption } from './episodes-toggle/EpisodesToggle.constants'; -export const EpisodesPageStore = (episodesResults: ApiResponse) => - observable({ - globalStore: getGlobalStore(), - sortType: EpisodesToggleOption.ASC, - episodesResults: episodesResults, - - get sortedTableData(): IEpisodeTable[] { - return orderBy(this.generateTableData, 'title', this.sortType); - }, - - get generateTableData(): IEpisodeTable[] { - if (this.episodesResults.error) { - return []; - } - - const seasons: { [season: string]: IEpisode[] } = groupBy(this.episodesResults.data, 'season'); - - return Object.entries(seasons).map(([season, models]) => { - return { - title: `Season ${season}`, - rows: models.map((model) => ({ - episode: model.number, - name: model.name, - date: dayjs(model.airdate).format('MMM D, YYYY'), - image: model.image?.medium ?? '', - })), - }; - }); - }, - - setSortType(sortType: EpisodesToggleOption): void { - this.sortType = sortType; - - this.globalStore.toastStore.enqueueToast('Nice! You just sorted Server-Side Rendered Content.', 'info'); - }, - }); - -export type EpisodesPageStore = ReturnType; +export class EpisodesPageStore { + globalStore = getGlobalStore(); + sortType = EpisodesToggleOption.ASC; + episodesResults: ApiResponse; + + constructor(episodesResults: ApiResponse) { + this.episodesResults = episodesResults; + + makeAutoObservable(this); + } + + get sortedTableData(): IEpisodeTable[] { + return orderBy(this.generateTableData, 'title', this.sortType); + } + + get generateTableData(): IEpisodeTable[] { + if (this.episodesResults.error) { + return []; + } + + const seasons: { [season: string]: IEpisode[] } = groupBy(this.episodesResults.data, 'season'); + + return Object.entries(seasons).map(([season, models]) => { + return { + title: `Season ${season}`, + rows: models.map((model) => ({ + episode: model.number, + name: model.name, + date: dayjs(model.airdate).format('MMM D, YYYY'), + image: model.image?.medium ?? '', + })), + }; + }); + } + + setSortType(sortType: EpisodesToggleOption): void { + this.sortType = sortType; + + this.globalStore.toastStore.enqueueToast('Nice! You just sorted Server-Side Rendered Content.', 'info'); + } +} diff --git a/src/components/pages/index-page/IndexPage.store.ts b/src/components/pages/index-page/IndexPage.store.ts index 360261e..c2819a1 100644 --- a/src/components/pages/index-page/IndexPage.store.ts +++ b/src/components/pages/index-page/IndexPage.store.ts @@ -1,4 +1,4 @@ -import { observable } from 'mobx'; +import { makeAutoObservable } from 'mobx'; import { getCastsRequest, getShowRequest } from '../../../domains/shows/shows.services'; import { initialResponseStatus } from '../../../utils/mobx.utils'; import { ICast, IShow } from '../../../domains/shows/shows.types'; @@ -6,51 +6,52 @@ import { ApiResponse } from '../../../utils/http/http.types'; import { defaultShowId } from '../../../domains/shows/shows.constants'; import orderBy from 'lodash.orderby'; -export const IndexPageStore = () => - observable({ - // globalStore: getGlobalStore(), - sortValue: '', - showResults: initialResponseStatus(null), - castsResults: initialResponseStatus([]), - - get isRequesting(): boolean { - return [this.showResults.isRequesting, this.castsResults.isRequesting].some(Boolean); - }, - - get actors(): ICast[] { - return orderBy(this.castsResults.data, (cast) => cast.person[this.sortValue], 'asc'); - }, - - setSortOption(sortValue: string): void { - this.sortValue = sortValue; - }, - - /** - * Store initializer. Should only be called once. - */ - *init() { - yield Promise.all([this.loadShow(), this.loadCasts()]); - }, - - *loadShow() { - const response: ApiResponse = yield getShowRequest(defaultShowId); - - this.showResults = { - data: this.showResults.data, - isRequesting: false, - ...response, // Overwrites the default data prop or adds an error. Also adds the statusCode. - }; - }, - - *loadCasts() { - const response: ApiResponse = yield getCastsRequest(defaultShowId); - - this.castsResults = { - data: this.castsResults.data, - isRequesting: false, - ...response, // Overwrites the default data prop or adds an error. Also adds the statusCode. - }; - }, - }); - -export type IndexPageStore = ReturnType; +export class IndexPageStore { + // globalStore: getGlobalStore(), + sortValue = ''; + showResults = initialResponseStatus(null); + castsResults = initialResponseStatus([]); + + constructor() { + makeAutoObservable(this); + } + + get isRequesting(): boolean { + return [this.showResults.isRequesting, this.castsResults.isRequesting].some(Boolean); + } + + get actors(): ICast[] { + return orderBy(this.castsResults.data, (cast) => cast.person[this.sortValue], 'asc'); + } + + setSortOption(sortValue: string) { + this.sortValue = sortValue; + } + + /** + * Store initializer. Should only be called once. + */ + *init() { + yield Promise.all([this.loadShow(), this.loadCasts()]); + } + + *loadShow() { + const response: ApiResponse = yield getShowRequest(defaultShowId); + + this.showResults = { + data: this.showResults.data, + isRequesting: false, + ...response, // Overwrites the default data prop or adds an error. Also adds the statusCode. + }; + } + + *loadCasts() { + const response: ApiResponse = yield getCastsRequest(defaultShowId); + + this.castsResults = { + data: this.castsResults.data, + isRequesting: false, + ...response, // Overwrites the default data prop or adds an error. Also adds the statusCode. + }; + } +} diff --git a/src/pages/episodes/[episode_id].tsx b/src/pages/episodes/[episode_id].tsx index f86af8f..27e8c4d 100644 --- a/src/pages/episodes/[episode_id].tsx +++ b/src/pages/episodes/[episode_id].tsx @@ -13,7 +13,7 @@ interface IProps { } const EpisodesRoute: NextPage = observer((props) => { - const [localStore] = useState(EpisodesPageStore(props.episodesResults)); + const [localStore] = useState(new EpisodesPageStore(props.episodesResults)); return ( diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 6fa513b..711a69a 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react-lite'; interface IProps {} const IndexRoute: NextPage = observer((props) => { - const [localStore] = useState(IndexPageStore()); + const [localStore] = useState(new IndexPageStore()); useEffect(() => { localStore.init(); diff --git a/src/stores/GlobalStore.ts b/src/stores/GlobalStore.ts index 8676245..13bf450 100644 --- a/src/stores/GlobalStore.ts +++ b/src/stores/GlobalStore.ts @@ -19,8 +19,8 @@ export default class GlobalStore { readonly toastStore: ToastGlobalStore; constructor() { - this.authStore = AuthGlobalStore(this); - this.toastStore = ToastGlobalStore(this); + this.authStore = new AuthGlobalStore(this); + this.toastStore = new ToastGlobalStore(this); } async hydrate(initialState?: Partial) { diff --git a/src/stores/auth/AuthGlobalStore.ts b/src/stores/auth/AuthGlobalStore.ts index 6423adc..8d6a29a 100644 --- a/src/stores/auth/AuthGlobalStore.ts +++ b/src/stores/auth/AuthGlobalStore.ts @@ -1,4 +1,4 @@ -import { observable } from 'mobx'; +import { makeAutoObservable } from 'mobx'; import GlobalStore from '../GlobalStore'; import { getUserRequest } from '../../domains/auth/auth.services'; import { ApiResponse } from '../../utils/http/http.types'; @@ -7,47 +7,51 @@ import { Routes } from '../../constants/Routes'; import { IUser, IUserResponse } from '../../domains/auth/auth.types'; import Router from 'next/router'; -export const AuthGlobalStore = (globalStore: GlobalStore) => - observable({ - authResults: initialResponseStatus(null, false), +export class AuthGlobalStore { + globalStore: GlobalStore; + authResults = initialResponseStatus(null, false); - get isAuthenticated(): boolean { - return Boolean(this.authResults.data); - }, + constructor(globalStore: GlobalStore) { + this.globalStore = globalStore; - get user(): IUser | null { - if (this.authResults.data) { - return this.authResults.data.results[0]; - } + makeAutoObservable(this); + } - return null; - }, + get isAuthenticated(): boolean { + return Boolean(this.authResults.data); + } - get userFullName(): string { - return `${this.user?.name?.first} ${this.user?.name?.last}`.trim(); - }, + get user(): IUser | null { + if (this.authResults.data) { + return this.authResults.data.results[0]; + } - *signIn() { - this.authResults.isRequesting = true; + return null; + } - const response: ApiResponse = yield getUserRequest(); + get userFullName(): string { + return `${this.user?.name?.first} ${this.user?.name?.last}`.trim(); + } - this.authResults = { - data: this.authResults.data, - isRequesting: false, - ...response, // Overwrites the default data prop or adds an error. Also adds the statusCode. - }; + *signIn() { + this.authResults.isRequesting = true; - if (this.user) { - globalStore.toastStore.enqueueToast(`Welcome ${this.userFullName}`, 'success'); - } - }, + const response: ApiResponse = yield getUserRequest(); - signOut(): void { - this.authResults = initialResponseStatus(null, false); + this.authResults = { + data: this.authResults.data, + isRequesting: false, + ...response, // Overwrites the default data prop or adds an error. Also adds the statusCode. + }; - Router.router?.push(Routes.Index); - }, - }); + if (this.user) { + this.globalStore.toastStore.enqueueToast(`Welcome ${this.userFullName}`, 'success'); + } + } -export type AuthGlobalStore = ReturnType; + signOut(): void { + this.authResults = initialResponseStatus(null, false); + + Router.router?.push(Routes.Index); + } +} diff --git a/src/stores/toast/ToastGlobalStore.ts b/src/stores/toast/ToastGlobalStore.ts index 06cdd08..15d4dbd 100644 --- a/src/stores/toast/ToastGlobalStore.ts +++ b/src/stores/toast/ToastGlobalStore.ts @@ -1,29 +1,30 @@ -import { IObservableArray, observable, runInAction } from 'mobx'; +import { IObservableArray, makeAutoObservable, runInAction } from 'mobx'; import GlobalStore from '../GlobalStore'; import { IToastNotification } from '../../components/ui/toast-notifier/ToastNotifier.types'; import { VariantType } from 'notistack'; -export const ToastGlobalStore = (globalStore: GlobalStore) => - observable({ - notifications: ([] as unknown) as IObservableArray, +export class ToastGlobalStore { + notifications = ([] as unknown) as IObservableArray; - enqueueToast(message: string, variantType: VariantType) { - const keyId = new Date().toString(); + constructor(globalStore: GlobalStore) { + makeAutoObservable(this); + } - this.notifications.push({ - message, - options: { - key: keyId, - variant: variantType as VariantType, - }, - }); - }, + enqueueToast(message: string, variantType: VariantType) { + const keyId = new Date().toString(); - removeToast(notification: IToastNotification) { - runInAction(() => { - this.notifications.remove(notification); - }); - }, - }); + this.notifications.push({ + message, + options: { + key: keyId, + variant: variantType as VariantType, + }, + }); + } -export type ToastGlobalStore = ReturnType; + removeToast(notification: IToastNotification) { + runInAction(() => { + this.notifications.remove(notification); + }); + } +} From 38cd7c153221928275da95f1d94d0b4ecd065257 Mon Sep 17 00:00:00 2001 From: robertsavian Date: Thu, 28 Jan 2021 12:31:10 -0600 Subject: [PATCH 2/4] fix import path --- src/stores/auth/AuthGlobalStore.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stores/auth/AuthGlobalStore.ts b/src/stores/auth/AuthGlobalStore.ts index 8d6a29a..2ce66c1 100644 --- a/src/stores/auth/AuthGlobalStore.ts +++ b/src/stores/auth/AuthGlobalStore.ts @@ -3,7 +3,7 @@ import GlobalStore from '../GlobalStore'; import { getUserRequest } from '../../domains/auth/auth.services'; import { ApiResponse } from '../../utils/http/http.types'; import { initialResponseStatus } from '../../utils/mobx.utils'; -import { Routes } from '../../constants/Routes'; +import { Routes } from '../../constants/Routes.constants'; import { IUser, IUserResponse } from '../../domains/auth/auth.types'; import Router from 'next/router'; @@ -46,6 +46,8 @@ export class AuthGlobalStore { if (this.user) { this.globalStore.toastStore.enqueueToast(`Welcome ${this.userFullName}`, 'success'); + } else { + this.globalStore.toastStore.enqueueToast('Sign In Issue. Try Again.', 'error'); } } From a6c9e40901bdb280ae189da034a3aaf4d6bb2435 Mon Sep 17 00:00:00 2001 From: robertsavian Date: Fri, 26 Feb 2021 16:51:16 -0600 Subject: [PATCH 3/4] Convert AboutPageStore to factory function store --- .../pages/about-page/AboutPage.store.ts | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/components/pages/about-page/AboutPage.store.ts b/src/components/pages/about-page/AboutPage.store.ts index 9729f4b..175a10e 100644 --- a/src/components/pages/about-page/AboutPage.store.ts +++ b/src/components/pages/about-page/AboutPage.store.ts @@ -1,37 +1,36 @@ -import { makeAutoObservable } from 'mobx'; +import { observable } from 'mobx'; import { getErrorRequest } from '../../../domains/shows/shows.services'; import { initialResponseStatus } from '../../../utils/mobx.utils'; import { ApiResponse } from '../../../utils/http/http.types'; import { getGlobalStore } from '../../shared/global-store-provider/GlobalStoreProvider'; -export class AboutPageStore { - globalStore = getGlobalStore(); - errorExampleResults = initialResponseStatus(null); +export const AboutPageStore = () => + observable({ + globalStore: getGlobalStore(), + errorExampleResults: initialResponseStatus(null), - constructor() { - makeAutoObservable(this); - } + /** + * Store initializer. Should only be called once. + */ + *init() { + yield Promise.all([this.loadSomething()]); + }, - /** - * Store initializer. Should only be called once. - */ - *init() { - yield Promise.all([this.loadSomething()]); - } + *loadSomething() { + const response: ApiResponse = yield getErrorRequest(); - *loadSomething() { - const response: ApiResponse = yield getErrorRequest(); + this.errorExampleResults = { + data: this.errorExampleResults.data, + isRequesting: false, + ...response, // Overwrites the default data prop or adds an error. Also adds the statusCode. + }; - this.errorExampleResults = { - data: this.errorExampleResults.data, - isRequesting: false, - ...response, // Overwrites the default data prop or adds an error. Also adds the statusCode. - }; + if (response.error) { + const message = `${response.statusCode}: ${response.error.message}`; - if (response.error) { - const message = `${response.statusCode}: ${response.error.message}`; + this.globalStore.toastStore.enqueueToast(message, 'error'); + } + }, + }); - this.globalStore.toastStore.enqueueToast(message, 'error'); - } - } -} +export type AboutPageStore = ReturnType; From b88952d6cec534ec3de05bf975ddb9abaf2a220b Mon Sep 17 00:00:00 2001 From: robertsavian Date: Fri, 26 Feb 2021 16:51:45 -0600 Subject: [PATCH 4/4] Update types on stores --- src/components/pages/episodes-page/EpisodesPage.store.ts | 2 +- src/components/pages/index-page/IndexPage.store.ts | 3 ++- src/pages/about/index.tsx | 2 +- src/stores/auth/AuthGlobalStore.ts | 2 +- src/stores/toast/ToastGlobalStore.ts | 3 +++ 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/pages/episodes-page/EpisodesPage.store.ts b/src/components/pages/episodes-page/EpisodesPage.store.ts index 74ca060..e5886c8 100644 --- a/src/components/pages/episodes-page/EpisodesPage.store.ts +++ b/src/components/pages/episodes-page/EpisodesPage.store.ts @@ -8,7 +8,7 @@ import { getGlobalStore } from '../../shared/global-store-provider/GlobalStorePr import { EpisodesToggleOption } from './episodes-toggle/EpisodesToggle.constants'; export class EpisodesPageStore { - globalStore = getGlobalStore(); + readonly globalStore = getGlobalStore(); sortType = EpisodesToggleOption.ASC; episodesResults: ApiResponse; diff --git a/src/components/pages/index-page/IndexPage.store.ts b/src/components/pages/index-page/IndexPage.store.ts index c2819a1..2d94716 100644 --- a/src/components/pages/index-page/IndexPage.store.ts +++ b/src/components/pages/index-page/IndexPage.store.ts @@ -5,9 +5,10 @@ import { ICast, IShow } from '../../../domains/shows/shows.types'; import { ApiResponse } from '../../../utils/http/http.types'; import { defaultShowId } from '../../../domains/shows/shows.constants'; import orderBy from 'lodash.orderby'; +import { getGlobalStore } from '../../shared/global-store-provider/GlobalStoreProvider'; export class IndexPageStore { - // globalStore: getGlobalStore(), + readonly globalStore = getGlobalStore(); sortValue = ''; showResults = initialResponseStatus(null); castsResults = initialResponseStatus([]); diff --git a/src/pages/about/index.tsx b/src/pages/about/index.tsx index 80c83a6..64032a4 100644 --- a/src/pages/about/index.tsx +++ b/src/pages/about/index.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react-lite'; interface IProps {} const AboutRoute: NextPage = observer((props) => { - const [localStore] = useState(new AboutPageStore()); + const [localStore] = useState(AboutPageStore()); useEffect(() => { localStore.init(); diff --git a/src/stores/auth/AuthGlobalStore.ts b/src/stores/auth/AuthGlobalStore.ts index 2ce66c1..8fddd40 100644 --- a/src/stores/auth/AuthGlobalStore.ts +++ b/src/stores/auth/AuthGlobalStore.ts @@ -8,7 +8,7 @@ import { IUser, IUserResponse } from '../../domains/auth/auth.types'; import Router from 'next/router'; export class AuthGlobalStore { - globalStore: GlobalStore; + readonly globalStore: GlobalStore; authResults = initialResponseStatus(null, false); constructor(globalStore: GlobalStore) { diff --git a/src/stores/toast/ToastGlobalStore.ts b/src/stores/toast/ToastGlobalStore.ts index 15d4dbd..2e8d6be 100644 --- a/src/stores/toast/ToastGlobalStore.ts +++ b/src/stores/toast/ToastGlobalStore.ts @@ -4,9 +4,12 @@ import { IToastNotification } from '../../components/ui/toast-notifier/ToastNoti import { VariantType } from 'notistack'; export class ToastGlobalStore { + readonly globalStore: GlobalStore; notifications = ([] as unknown) as IObservableArray; constructor(globalStore: GlobalStore) { + this.globalStore = globalStore; + makeAutoObservable(this); }