Skip to content

Commit

Permalink
crazy refactor to improve things too hard to explain
Browse files Browse the repository at this point in the history
  • Loading branch information
tlgimenes committed Jun 15, 2022
1 parent 3bb38e5 commit 1f51cb0
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 204 deletions.
2 changes: 1 addition & 1 deletion packages/api/src/platforms/vtex/resolvers/offer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const StoreOffer: Record<string, Resolver<Root>> = {
},
listPrice: (root) => {
if (isSearchItem(root)) {
return root.ListPrice
return root.ListPrice ?? 0
}

if (isOrderFormItem(root)) {
Expand Down
7 changes: 7 additions & 0 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ export { parse as parseSearchState } from './search/serializer'
export { default as formatSearchState } from './utils/format'

export { initialize as initSearchState } from './search/useSearchState'
export {
isSearchSort,
removeFacet,
setFacet,
toggleFacet,
toggleFacets,
} from './search/facets'
export { Provider as SearchProvider } from './search/Provider'
export { useSearch } from './search/useSearch'
export { usePagination } from './search/usePagination'
Expand Down
59 changes: 59 additions & 0 deletions packages/sdk/src/search/facets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { SDKError } from '../utils/error'
import type { Facet, SearchSort } from '../types'

const sortKeys = new Set<SearchSort>([
'price_desc',
'price_asc',
'orders_desc',
'name_desc',
'name_asc',
'release_desc',
'discount_desc',
'score_desc',
])

export const isSearchSort = (x: string): x is SearchSort =>
sortKeys.has(x as any)

export const removeFacet = (facets: Facet[], facet: Facet): Facet[] => {
const { value } = facet

const index = facets.findIndex((x) => x.value === value)

if (index < 0) {
throw new SDKError(`Cannot remove ${value} from search params`)
}

return facets.filter((_, it) => it === 0 || it !== index)
}

export const setFacet = (
facets: Facet[],
facet: Facet,
unique?: boolean
): Facet[] => {
if (unique === true) {
const index = facets.findIndex((f) => f.key === facet.key)

if (index > -1) {
return facets.map((f, it) => (it === index ? facet : f))
}
}

return [...facets, facet]
}

export const toggleFacet = (facets: Facet[], item: Facet) => {
const found = facets.find(
(facet) => facet.key === item.key && facet.value === item.value
)

if (found !== undefined) {
return removeFacet(facets, item)
}

return setFacet(facets, item, false)
}

export const toggleFacets = (facets: Facet[], items: Facet[]) =>
items.reduce((acc, curr) => toggleFacet(acc, curr), facets)
16 changes: 11 additions & 5 deletions packages/sdk/src/search/serializer.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import { SDKError } from '../utils/error'
import { isSearchSort, setFacet } from './facets'
import { initialize } from './useSearchState'
import type { SearchSort, State } from '../types'
import { initialize, reducer } from './useSearchState'

export const parse = ({ pathname, searchParams }: URL): State => {
let state = initialize({
const state = initialize({
base: pathname,
term: searchParams.get('q') ?? null,
sort: (searchParams.get('sort') as SearchSort) ?? undefined,
page: Number(searchParams.get('page') ?? 0),
})

if (!isSearchSort(state.sort)) {
throw new SDKError(`Uknown sorting option ${state.sort}`)
}

const facets = searchParams.get('facets')?.split(',') ?? []

for (const facet of facets) {
const values = searchParams.getAll(facet)

for (const value of values) {
state = reducer(state, {
type: 'setFacet' as const,
payload: { facet: { key: facet, value }, unique: false },
state.selectedFacets = setFacet(state.selectedFacets, {
key: facet,
value,
})
}
}
Expand Down
23 changes: 17 additions & 6 deletions packages/sdk/src/search/useInfiniteSearchState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable no-case-declarations */
import { useMemo, useReducer } from 'react'

import { SDKError } from '../utils/error'
Expand All @@ -12,33 +11,45 @@ type Action =
| {
type: 'addNext'
}
| {
type: 'reset'
payload: number
}

const reducer = (state: State, action: Action) => {
switch (action.type) {
case 'addPrev':
case 'addPrev': {
const prev = state[0] - 1

return [prev, ...state]
}

case 'addNext':
case 'addNext': {
const next = Number(state[state.length - 1]) + 1

return [...state, next]
}

case 'reset': {
const { payload } = action

return [payload]
}

default:
throw new SDKError('Unknown action for infinite search')
}
}

export const useSearchInfiniteState = (initialPage: number) => {
const [pages, dispatch] = useReducer(reducer, initialPage, () => [
initialPage,
])
const [pages, dispatch] = useReducer(reducer, undefined, () => [initialPage])

const actions = useMemo(
() => ({
addPrevPage: () => dispatch({ type: 'addPrev' }),
addNextPage: () => dispatch({ type: 'addNext' }),
resetInfiniteScroll: (page: number) =>
dispatch({ type: 'reset', payload: page }),
}),
[]
)
Expand Down
200 changes: 8 additions & 192 deletions packages/sdk/src/search/useSearchState.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
import { useMemo } from 'react'

import type { Facet, SearchSort, State } from '../types'
import { SDKError } from '../utils/error'
import format from '../utils/format'

const sortKeys = new Set<SearchSort>([
'price_desc',
'price_asc',
'orders_desc',
'name_desc',
'name_asc',
'release_desc',
'discount_desc',
'score_desc',
])
import type { State } from '../types'

export const initialize = ({
sort = 'score_desc',
Expand All @@ -29,194 +17,22 @@ export const initialize = ({
page,
})

const isSearchSort = (x: string): x is SearchSort => sortKeys.has(x as any)

type Action =
| {
type: 'setSort'
payload: SearchSort
}
| {
type: 'setTerm'
payload: string | null
}
| {
type: 'setPage'
payload: number
}
| {
type: 'setFacet'
payload: { facet: Facet; unique: boolean }
}
| {
type: 'setFacets'
payload: Facet[]
}
| {
type: 'removeFacet'
payload: Facet
}
| {
type: 'toggleFacet'
payload: Facet
}
| {
type: 'toggleFacets'
payload: Facet[]
}

const equals = (s1: State, s2: State) => format(s1).href === format(s2).href

const removeFacet = (state: State, facet: Facet): State => {
const { value } = facet

const index = state.selectedFacets.findIndex((x) => x.value === value)

if (index < 0) {
throw new SDKError(`Cannot remove ${value} from search params`)
}

// We can't allow removing the first facet, otherwise we would loose
// the navigation context
//
// TODO: Remove returning the base selected facets to the frontend since
// we won't be unselecting it anyways
return {
...state,
selectedFacets: state.selectedFacets.filter(
(_, it) => it === 0 || it !== index
),
}
}

const setFacet = (state: State, facet: Facet, unique: boolean): State => {
if (unique === true) {
const index = state.selectedFacets.findIndex((f) => f.key === facet.key)

if (index > -1) {
return {
...state,
selectedFacets: state.selectedFacets.map((f, it) =>
it === index ? facet : f
),
}
}
}

return {
...state,
selectedFacets: [...state.selectedFacets, facet],
}
}

const toggleFacet = (state: State, item: Facet) => {
const found = state.selectedFacets.find(
(facet) => facet.key === item.key && facet.value === item.value
)

if (found !== undefined) {
return removeFacet(state, item)
}

return setFacet(state, item, false)
}

const toggleFacets = (state: State, items: Facet[]) =>
items.reduce((item, s) => toggleFacet(item, s), state)

export const reducer = (state: State, action: Action) => {
switch (action.type) {
case 'setSort':
if (!isSearchSort(action.payload)) {
throw new SDKError(`Sort param ${action.payload} is unknown`)
}

return state.sort === action.payload
? state
: {
...state,
sort: action.payload,
}

case 'setTerm':
return state.term === action.payload
? state
: {
...state,
term: action.payload,
}

case 'setPage':
return state.page === action.payload
? state
: {
...state,
page: action.payload,
}

case 'setFacet':
return setFacet(state, action.payload.facet, action.payload.unique)

case 'setFacets':
return state.selectedFacets !== action.payload
? { ...state, selectedFacets: action.payload }
: state

case 'removeFacet':
return removeFacet(state, action.payload)

case 'toggleFacet':
return toggleFacet(state, action.payload)

case 'toggleFacets':
return toggleFacets(state, action.payload)

default:
throw new SDKError(`Unknown action of search state machine`)
}
}

const dispatcher = (onChange: (url: URL) => void, state: State) => (
action: Action
) => {
const newState = reducer(state, action)

if (!equals(newState, state)) {
onChange(format(newState))
}
}

export const useSearchState = (
initialState: Partial<State>,
onChange: (url: URL) => void
) => {
const state = useMemo(() => initialize(initialState), [initialState])

return useMemo(() => {
const dispatch = dispatcher(onChange, state)

return {
return useMemo(
() => ({
state,
setSort: (sort: SearchSort) =>
dispatch({ type: 'setSort', payload: sort }),
setTerm: (term: string | null) =>
dispatch({ type: 'setTerm', payload: term }),
setPage: (page: number) => dispatch({ type: 'setPage', payload: page }),
setFacet: (facet: Facet, unique = false) =>
dispatch({ type: 'setFacet', payload: { facet, unique } }),
setFacets: (facets: Facet[]) =>
dispatch({ type: 'setFacets', payload: facets }),
removeFacet: (facet: Facet) =>
dispatch({ type: 'removeFacet', payload: facet }),
toggleFacet: (facet: Facet) =>
dispatch({
type: 'toggleFacet',
payload: facet,
}),
toggleFacets: (facets: Facet[]) =>
dispatch({ type: 'toggleFacets', payload: facets }),
}
}, [onChange, state])
setState: (newState: State) =>
!equals(newState, state) && onChange(format(newState)),
}),
[onChange, state]
)
}

export type UseSearchState = ReturnType<typeof useSearchState>

0 comments on commit 1f51cb0

Please sign in to comment.