diff --git a/apps/web/src/core/@types/SearchProps.ts b/apps/web/src/core/@types/SearchProps.ts index 2c95ab1c7..1e2307333 100644 --- a/apps/web/src/core/@types/SearchProps.ts +++ b/apps/web/src/core/@types/SearchProps.ts @@ -5,4 +5,5 @@ export interface SearchProps { skip: number showOnEmptyQuery?: boolean modeLock?: 'list' | 'nh' + target: 'collection' | 'search' } diff --git a/apps/web/src/core/components/search.tsx b/apps/web/src/core/components/search.tsx index 8419d2af1..46769edab 100644 --- a/apps/web/src/core/components/search.tsx +++ b/apps/web/src/core/components/search.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useState } from 'react' import { chunk, get } from 'lodash-es' @@ -6,6 +6,8 @@ import { getSearch } from '@rayriffy-h/helper' import * as searchHentaiWorker from '../services/worker/searchHentai.worker' +import { useStoreon } from '../../store' + import { Listing } from './listing' import { Pagination } from './pagination' @@ -13,33 +15,30 @@ import { ListingHentai } from '../@types/ListingHentai' import { SearchProps } from '../@types/SearchProps' export const Search: React.FC = props => { - const { raw, skip, showOnEmptyQuery = false, modeLock } = props - - // State for observing raw input - const [input, setInput] = useState('') + const { raw, skip, showOnEmptyQuery = false, modeLock, target } = props - // Lock search query when page changes and text input change as well - const [query, setQuery] = useState('') + const { dispatch, search } = useStoreon('search') - // Set search dialog to be show on first time or not - const [first, setFirst] = useState(true) + console.log(search) - // Put all searh results in here (for listing) - const [res, setRes] = useState(showOnEmptyQuery ? raw : []) + const { input, query, first, res, page, maxPage, renderedRaw, mode } = search[ + target + ] // Status state const [loading, setLoading] = useState(false) const [error, setError] = useState(null) - // Stuff to be rendered - const [page, setPage] = useState(1) - const [maxPage, setMaxPage] = useState(5) - const [renderedRaw, setRenderedRaw] = useState([]) - - // Set mode between search from list or nhentai (if modeLock is present, then hide the selector and lock search into that mode) - const [mode, setMode] = useState<'list' | 'nh'>( - modeLock === undefined ? 'list' : modeLock - ) + // Set mode + const setMode = (mode: 'list' | 'nh') => + dispatch('search/update', { + target, + value: { + mode, + first: true, + res: [], + }, + }) const { searchHentai } = typeof window === 'object' @@ -49,20 +48,28 @@ export const Search: React.FC = props => { const renderPage = async (raws: ListingHentai[], page: number) => { if (mode === 'list') { - setPage(page) - setMaxPage(chunk(raws, skip).length) - setRenderedRaw(get(chunk(raws, skip), page - 1, [])) + dispatch('search/update', { + target, + value: { + page, + maxPage: chunk(raws, skip).length, + renderedRaw: get(chunk(raws, skip), page - 1, []), + }, + }) } else if (mode === 'nh') { setLoading(true) try { const res = await getSearch(query, page) - setPage(page) - setRenderedRaw( - res.raw.map(o => ({ - raw: o, - internal: false, - })) - ) + dispatch('search/update', { + target, + value: { + page, + renderedRaw: res.raw.map(o => ({ + raw: o, + internal: false, + })), + }, + }) } catch { setError('Unable to retrieve data from server') } finally { @@ -73,61 +80,59 @@ export const Search: React.FC = props => { const searchHandler = async () => { if (input === '') { - setQuery('') - setRes(showOnEmptyQuery && mode === 'list' ? raw : []) + dispatch('search/update', { + target, + value: { + query: '', + res: showOnEmptyQuery && mode === 'list' ? raw : [], + }, + }) + + if (showOnEmptyQuery && mode === 'list') { + renderPage(res, 1) + } } else { setLoading(true) - setFirst(false) setError(null) - setQuery(input) + dispatch('search/update', { + target, + value: { + first: false, + query: input, + }, + }) if (mode === 'list') { const res = await searchHentai(input, raw) - setRes(res) + dispatch('search/update', { + target, + value: { + res, + }, + }) + + renderPage(res, 1) } else if (mode === 'nh') { const res = await getSearch(input, 1) - setMaxPage(res.maxPage) - setRes( - res.raw.map(o => ({ - raw: o, - internal: false, - })) - ) - - // Manually render first page - setPage(1) - setRenderedRaw( - res.raw.map(o => ({ - raw: o, - internal: false, - })) - ) + const transformedRes = res.raw.map(o => ({ + raw: o, + internal: false, + })) + dispatch('search/update', { + target, + value: { + page: 1, + maxPage: res.maxPage, + res: transformedRes, + renderedRaw: transformedRes, + }, + }) } setLoading(false) } } - useEffect(() => { - if (res.length !== 0 && mode === 'list') { - setPage(1) - renderPage(res, 1) - } - }, [res]) - - useEffect(() => { - if (showOnEmptyQuery) { - setRes(raw) - } - }, [raw]) - - useEffect(() => { - setRes(showOnEmptyQuery && mode === 'list' ? raw : []) - - // When - setFirst(true) - }, [mode]) - return (
@@ -141,7 +146,12 @@ export const Search: React.FC = props => { onKeyDown={e => (e.keyCode === 13 ? searchHandler() : null)} enterkeyhint="🔎" onChange={({ target: { value } }) => { - setInput(value) + dispatch('search/update', { + target, + value: { + input: value, + }, + }) }} />
@@ -208,7 +218,7 @@ export const Search: React.FC = props => {
)} - {res.length > 0 ? ( + {res.length > 0 && !loading ? ( { + const didMount = useRef(false) + + useEffect(() => { + if (didMount.current) func() + else didMount.current = true + }, deps) +} diff --git a/apps/web/src/gatsby/browser/index.ts b/apps/web/src/gatsby/browser/index.ts index e9d017c0a..f96e94caf 100644 --- a/apps/web/src/gatsby/browser/index.ts +++ b/apps/web/src/gatsby/browser/index.ts @@ -1,6 +1,17 @@ +import React from 'react' + export * from './onServiceWorkerInstalled' export * from './onServiceWorkerUpdateFound' export * from './onServiceWorkerUpdateReady' // export * from './replaceHydrateFunction' export * from './wrapPageElement' export * from './wrapRootElement' + +export const onClientEntry = () => { + if (process.env.NODE_ENV !== 'production') { + const whyDidYouRender = require('@welldone-software/why-did-you-render') + whyDidYouRender(React, { + trackAllPureComponents: true + }) + } +} diff --git a/apps/web/src/gatsby/config.ts b/apps/web/src/gatsby/config.ts index 0e8269150..3f8ff7859 100644 --- a/apps/web/src/gatsby/config.ts +++ b/apps/web/src/gatsby/config.ts @@ -9,6 +9,7 @@ const config: GatsbyConfig = { }, pathPrefix: '/', plugins: [ + `gatsby-plugin-why-did-you-render`, `gatsby-plugin-postcss`, { resolve: require.resolve(`@nrwl/gatsby/plugins/nx-gatsby-ext-plugin`), diff --git a/apps/web/src/store/@types/SearchEvent.ts b/apps/web/src/store/@types/SearchEvent.ts new file mode 100644 index 000000000..5b26407e2 --- /dev/null +++ b/apps/web/src/store/@types/SearchEvent.ts @@ -0,0 +1,9 @@ +import { SearchStore, SearchCache } from './SearchStore' + +export interface SearchEvent { + 'search/update': { + target: 'collection' | 'search' + value: Partial + } + 'search/override': SearchStore +} diff --git a/apps/web/src/store/@types/SearchStore.ts b/apps/web/src/store/@types/SearchStore.ts new file mode 100644 index 000000000..1658e80b0 --- /dev/null +++ b/apps/web/src/store/@types/SearchStore.ts @@ -0,0 +1,25 @@ +import { ListingHentai } from '../../core/@types/ListingHentai' + +export interface SearchCache { + // State for observing raw input + input: string + // Lock search query when page changes and text input change as well + query: string + // Set search dialog to be show on first time or not + first: boolean + // Put all searh results in here (for listing) + res: ListingHentai[] + // Set mode between search from list or nhentai (if modeLock is present, then hide the selector and lock search into that mode) + mode: 'list' | 'nh' + // Stuff to be rendered + page: number + maxPage: number + renderedRaw: ListingHentai[] +} + +export interface SearchStore { + search: { + search: SearchCache + collection: SearchCache + } +} diff --git a/apps/web/src/store/index.tsx b/apps/web/src/store/index.tsx index 4e2164e20..c016cd8be 100644 --- a/apps/web/src/store/index.tsx +++ b/apps/web/src/store/index.tsx @@ -10,7 +10,12 @@ export const useStoreon = customContext(StoreonContext) export const Context: React.FC = props => { const { children } = props + const clientSession = Math.random().toString(36).substring(3) + return ( - {children} + + {clientSession} + {children} + ) } diff --git a/apps/web/src/store/states/search.ts b/apps/web/src/store/states/search.ts new file mode 100644 index 000000000..ddb0246f6 --- /dev/null +++ b/apps/web/src/store/states/search.ts @@ -0,0 +1,57 @@ +import { StoreonModule } from 'storeon' + +import { SearchStore, SearchCache } from '../@types/SearchStore' +import { SearchEvent } from '../@types/SearchEvent' + +export const search: StoreonModule = store => { + store.on('@init', () => { + const defaultState: SearchCache = { + input: '', + query: '', + first: true, + res: [], + page: 1, + maxPage: 5, + renderedRaw: [], + mode: 'list', + } + + return { + search: { + search: defaultState, + collection: defaultState, + } + } + }) + + store.on('search/update', (state, event) => { + // eslint-disable-next-line prefer-const + let payload = { + ...state + } + + payload.search[event.target] = { + ...payload.search[event.target], + ...event.value, + } + + return payload + }) + + // store.on('search/toggle', (state, event) => { + // switch (event) { + // case 'safemode': + // return { + // ...state, + // settings: { + // ...state.settings, + // safemode: !state.settings.safemode, + // }, + // } + // default: + // return state + // } + // }) + + store.on('search/override', (state, event) => event) +} diff --git a/apps/web/src/store/storeon.ts b/apps/web/src/store/storeon.ts index 026c461a5..55324138a 100644 --- a/apps/web/src/store/storeon.ts +++ b/apps/web/src/store/storeon.ts @@ -19,24 +19,34 @@ import { history } from './states/history' import { HistoryStore } from './@types/HistoryStore' import { HistoryEvent } from './@types/HistoryEvent' +import { search } from './states/search' +import { SearchStore } from './@types/SearchStore' +import { SearchEvent } from './@types/SearchEvent' + export type Store = CollectionStore & SettingsStore & SubtitleStore & - HistoryStore + HistoryStore & + SearchStore export type Event = CollectionEvent & SettingsEvent & SubtitleEvent & - HistoryEvent + HistoryEvent & + SearchEvent export const store = createStoreon([ settings, collection, subtitle, history, + search, ...(typeof window !== 'undefined' ? [ persistState(['settings', 'collection', 'history']), - crossTab({ filter: (event, data) => event !== 'subtitle/setSubtitle' }), + persistState(['search'], { + storage: sessionStorage, + }), + crossTab({ filter: (event, data) => event !== 'subtitle/setSubtitle' || event.startsWith('search/') }), ] : []), ]) diff --git a/apps/web/src/templates/collection/components/index.tsx b/apps/web/src/templates/collection/components/index.tsx index fab36aa2e..105189a0e 100644 --- a/apps/web/src/templates/collection/components/index.tsx +++ b/apps/web/src/templates/collection/components/index.tsx @@ -38,6 +38,7 @@ const Page: React.FC = props => { internal: o.internal, }))} skip={skip} + target="collection" modeLock="list" showOnEmptyQuery /> diff --git a/apps/web/src/templates/search/components/index.tsx b/apps/web/src/templates/search/components/index.tsx index a6f7a435e..7a244ffc4 100644 --- a/apps/web/src/templates/search/components/index.tsx +++ b/apps/web/src/templates/search/components/index.tsx @@ -22,6 +22,7 @@ const Page: React.FC = props => { ({ raw: o, internal: true, diff --git a/package.json b/package.json index 3aa8ae3d0..a45dfdd5d 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@fullhuman/postcss-purgecss": "^2.3.0", "@storeon/crosstab": "^1.0.2", "@storeon/localstorage": "^1.0.0", + "@welldone-software/why-did-you-render": "^4.2.5", "autoprefixer": "^9.8.2", "chrome-aws-lambda": "^3.1.1", "commander": "^5.1.0", @@ -58,6 +59,7 @@ "gatsby-plugin-sharp": "2.6.14", "gatsby-plugin-svgr": "2.0.2", "gatsby-plugin-typescript": "^2.4.6", + "gatsby-plugin-why-did-you-render": "^2.0.0", "gatsby-source-filesystem": "^2.3.14", "gatsby-transformer-sharp": "2.5.7", "isomorphic-unfetch": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index f68f12077..880959ba4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4884,6 +4884,13 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" +"@welldone-software/why-did-you-render@^4.0.4", "@welldone-software/why-did-you-render@^4.2.5": + version "4.2.5" + resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-4.2.5.tgz#005d06958d0c8a1ab3b92a3f4cfe3507b1806976" + integrity sha512-Uq4Gmi14bHDfWPGu1EuZKkdxlihXIyyfhAoP0nkA3qHTu8yit0iL1KO6Ldyno55otnjL3GHUY/MswMVvB5Ro8g== + dependencies: + lodash "^4" + "@wry/equality@^0.1.2": version "0.1.11" resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.11.tgz#35cb156e4a96695aa81a9ecc4d03787bc17f1790" @@ -10428,6 +10435,13 @@ gatsby-plugin-typescript@^2.4.6, gatsby-plugin-typescript@^2.4.8: "@babel/runtime" "^7.10.3" babel-plugin-remove-graphql-queries "^2.9.7" +gatsby-plugin-why-did-you-render@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/gatsby-plugin-why-did-you-render/-/gatsby-plugin-why-did-you-render-2.0.0.tgz#6521858a82a1c8b17a1f4ac2645871ea3d1f7b78" + integrity sha512-P5uLuiT50GhQ0Wxhfsd42cbcL6WF1Gpecq87xyByVuN0M5bUX4TMJWzbBPQWOSpU15+T9iPfdZHhlS/CSxw9KA== + dependencies: + "@welldone-software/why-did-you-render" "^4.0.4" + gatsby-react-router-scroll@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/gatsby-react-router-scroll/-/gatsby-react-router-scroll-3.0.7.tgz#53e9e4e81dfde94c23d724f8e6b4778db9d7edb1" @@ -13942,7 +13956,7 @@ lodash@4.17.11: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -lodash@4.17.15, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0: +lodash@4.17.15, lodash@^4, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==