Skip to content

Commit

Permalink
feat(web)!: preserve state when search
Browse files Browse the repository at this point in the history
  • Loading branch information
rayriffy committed Jun 29, 2020
1 parent c41a68d commit 0fc88ac
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 78 deletions.
1 change: 1 addition & 0 deletions apps/web/src/core/@types/SearchProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export interface SearchProps {
skip: number
showOnEmptyQuery?: boolean
modeLock?: 'list' | 'nh'
target: 'collection' | 'search'
}
156 changes: 83 additions & 73 deletions apps/web/src/core/components/search.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,44 @@
import React, { useEffect, useState } from 'react'
import React, { useState } from 'react'

import { chunk, get } from 'lodash-es'

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'

import { ListingHentai } from '../@types/ListingHentai'
import { SearchProps } from '../@types/SearchProps'

export const Search: React.FC<SearchProps> = props => {
const { raw, skip, showOnEmptyQuery = false, modeLock } = props

// State for observing raw input
const [input, setInput] = useState<string>('')
const { raw, skip, showOnEmptyQuery = false, modeLock, target } = props

// Lock search query when page changes and text input change as well
const [query, setQuery] = useState<string>('')
const { dispatch, search } = useStoreon('search')

// Set search dialog to be show on first time or not
const [first, setFirst] = useState<boolean>(true)
console.log(search)

// Put all searh results in here (for listing)
const [res, setRes] = useState<ListingHentai[]>(showOnEmptyQuery ? raw : [])
const { input, query, first, res, page, maxPage, renderedRaw, mode } = search[
target
]

// Status state
const [loading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<string | null>(null)

// Stuff to be rendered
const [page, setPage] = useState<number>(1)
const [maxPage, setMaxPage] = useState<number>(5)
const [renderedRaw, setRenderedRaw] = useState<ListingHentai[]>([])

// 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'
Expand All @@ -49,20 +48,28 @@ export const Search: React.FC<SearchProps> = 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 {
Expand All @@ -73,61 +80,59 @@ export const Search: React.FC<SearchProps> = 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 (
<React.Fragment>
<div className="flex justify-center pt-3">
Expand All @@ -141,7 +146,12 @@ export const Search: React.FC<SearchProps> = props => {
onKeyDown={e => (e.keyCode === 13 ? searchHandler() : null)}
enterkeyhint="🔎"
onChange={({ target: { value } }) => {
setInput(value)
dispatch('search/update', {
target,
value: {
input: value,
},
})
}}
/>
<div className="px-2" />
Expand Down Expand Up @@ -208,7 +218,7 @@ export const Search: React.FC<SearchProps> = props => {
</div>
</div>
)}
{res.length > 0 ? (
{res.length > 0 && !loading ? (
<React.Fragment>
<Pagination
current={page}
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/core/services/functions/useDidMountEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useEffect, useRef, EffectCallback, DependencyList } from 'react'

export const useDidMountEffect = (func: EffectCallback, deps: DependencyList) => {
const didMount = useRef(false)

useEffect(() => {
if (didMount.current) func()
else didMount.current = true
}, deps)
}
11 changes: 11 additions & 0 deletions apps/web/src/gatsby/browser/index.ts
Original file line number Diff line number Diff line change
@@ -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
})
}
}
1 change: 1 addition & 0 deletions apps/web/src/gatsby/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`),
Expand Down
9 changes: 9 additions & 0 deletions apps/web/src/store/@types/SearchEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SearchStore, SearchCache } from './SearchStore'

export interface SearchEvent {
'search/update': {
target: 'collection' | 'search'
value: Partial<SearchCache>
}
'search/override': SearchStore
}
25 changes: 25 additions & 0 deletions apps/web/src/store/@types/SearchStore.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
7 changes: 6 additions & 1 deletion apps/web/src/store/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<StoreonContext.Provider value={store}>{children}</StoreonContext.Provider>
<StoreonContext.Provider value={store}>
{clientSession}
{children}
</StoreonContext.Provider>
)
}
57 changes: 57 additions & 0 deletions apps/web/src/store/states/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { StoreonModule } from 'storeon'

import { SearchStore, SearchCache } from '../@types/SearchStore'
import { SearchEvent } from '../@types/SearchEvent'

export const search: StoreonModule<SearchStore, SearchEvent> = 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)
}
Loading

0 comments on commit 0fc88ac

Please sign in to comment.