Skip to content

Commit

Permalink
♻️ Move "browser" logic to separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
exah committed May 8, 2020
1 parent a3b5955 commit ede7b6a
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 118 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"size-limit": [
{
"path": "./esm/index.js",
"limit": "750B"
"limit": "700B"
}
],
"eslintConfig": {
Expand Down
1 change: 0 additions & 1 deletion src/constants.ts

This file was deleted.

5 changes: 0 additions & 5 deletions src/get-initial-data.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ import { useFetchData } from './use-fetch-data'
import { DataProvider } from './context'
import { createStore, defaultStore } from './store'

jest.mock('./constants', () => ({
...jest.requireActual('./constants'),
IS_SERVER: true,
}))

beforeEach(() => defaultStore.clear())

test('should render response', async () => {
Expand Down
5 changes: 2 additions & 3 deletions src/get-initial-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import { defaultStore } from './store'
*/

function getInitialData<T = any>(element: JSX.Element, store = defaultStore) {
store.clear()

return prepass(element).then<[Key, T][]>(() => store.flush())
store.purge()
return prepass(element).then<[Key, T][]>(() => Array.from(store))
}

export { getInitialData }
2 changes: 1 addition & 1 deletion src/index.browser.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { DataProvider } from './context'
export { createStore, hydrateInitialData } from './store'
export { useFetchData } from './use-fetch-data'
export { useFetchData } from './use-fetch-data.browser'
15 changes: 4 additions & 11 deletions src/store.test.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import { createStore, defaultStore, hydrateInitialData } from './store'

test('`creatStore` should return `Map` - like object', () => {
expect(createStore()).toMatchObject({
get: expect.any(Function),
set: expect.any(Function),
has: expect.any(Function),
clear: expect.any(Function),
delete: expect.any(Function),
flush: expect.any(Function),
size: 0,
})
test('`creatStore` should return `Map`', () => {
expect(createStore()).toBeInstanceOf(Map)
})

test('`defaultStore` should be empty', () => {
test('`defaultStore` should be empty `Map`', () => {
expect(defaultStore).toBeInstanceOf(Map)
expect(defaultStore.size).toBe(0)
})

Expand Down
72 changes: 30 additions & 42 deletions src/store.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,38 @@
import { Key } from './types'
import { IS_SERVER } from './constants'

export const createStore = <T = any>(input?: [Key, T][]) => {
const store = new Map(input)
export const createStore = <T = any>(init?: [Key, T][]) => {
const store = new Map(init)
const timer = new Map()

return {
has(key: Key) {
return store.has(key)
},
get(key: Key) {
return store.get(key)
},
set(key: Key, value: T, ttl?: number) {
if (timer.has(key)) {
clearTimeout(timer.get(key))
timer.delete(key)
}

if (ttl && !IS_SERVER) {
timer.set(
key,
setTimeout(() => {
store.delete(key)
timer.delete(key)
}, ttl)
)
}

return store.set(key, value)
},
delete(key: Key) {
store.delete(key)
},
get size() {
return store.size
},
clear() {
timer.forEach((id) => clearTimeout(id))
timer.clear()
store.clear()
},
flush() {
return Array.from(store)
},
function hasTTL(key: Key) {
return timer.has(key)
}

function deleteTTL(key: Key) {
clearTimeout(timer.get(key))
timer.delete(key)
}

function setTTL(key: Key, ttl?: number) {
deleteTTL(key)

if (ttl) {
timer.set(
key,
setTimeout(() => {
store.delete(key)
timer.delete(key)
}, ttl)
)
}
}

function purge() {
timer.forEach((_, key) => deleteTTL(key))
store.clear()
}

return Object.assign(store, { hasTTL, setTTL, purge })
}

export const defaultStore = createStore()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react-hooks'
import { defaultStore } from './store'
import { useFetchData } from './use-fetch-data'
import { useFetchData } from './use-fetch-data.browser'

const CLIENT_CONTEXT = { isServer: false }

Expand Down
58 changes: 58 additions & 0 deletions src/use-fetch-data.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useContext, useLayoutEffect } from 'react'
import { DataContext } from './context'
import { AsyncState, Key, Fetcher } from './types'
import { useAsyncState, finished } from './use-async-state'

/**
* Requests data and preserves the result to the state.
* The `key` must be unique for the whole application.
*
* @see https://github.com/exah/react-universal-data#useFetchData
*/

export function useFetchData<T = any>(
fetcher: Fetcher<T>,
key: Key,
ttl?: number
): AsyncState<T> {
const store = useContext(DataContext)
const init = store.has(key) ? finished<T>(store.get(key)) : null
const [state, actions] = useAsyncState<T>(init)

useLayoutEffect(() => {
if (store.has(key)) {
if (!ttl) {
store.delete(key)
} else if (!store.hasTTL(key)) {
store.setTTL(key, ttl)
}

return
}

let isCancelled = false
function finish(result: T) {
if (isCancelled) return

actions.finish(result)

if (!(result instanceof Error) && ttl) {
store.setTTL(key, ttl)
store.set(key, result)
}
}

function cleanup() {
isCancelled = true
}

actions.start()
Promise.resolve()
.then(() => fetcher(key, { isServer: false }))
.then(finish, finish)

return cleanup
}, [store, key, ttl, actions, fetcher])

return state
}
62 changes: 9 additions & 53 deletions src/use-fetch-data.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { useContext, useLayoutEffect } from 'react'
import { IS_SERVER } from './constants'
import { useContext } from 'react'
import { DataContext } from './context'
import { AsyncState, Key, Context, Fetcher } from './types'
import { useAsyncState, finished } from './use-async-state'

const CONTEXT: Context = { isServer: IS_SERVER }
import { AsyncState, Key, Fetcher } from './types'
import { finished } from './use-async-state'

/**
* Requests data and preserves the result to the state.
Expand All @@ -13,7 +10,7 @@ const CONTEXT: Context = { isServer: IS_SERVER }
* @see https://github.com/exah/react-universal-data#useFetchData
*/

function useFetchServerData<T = any>(
export function useFetchData<T = any>(
fetcher: Fetcher<T>,
key: Key,
ttl?: number
Expand All @@ -25,50 +22,9 @@ function useFetchServerData<T = any>(
}

throw Promise.resolve()
.then(() => fetcher(key, CONTEXT))
.then((result) => store.set(key, result, ttl))
}

function useFetchClientData<T = any>(
fetcher: Fetcher<T>,
key: Key,
ttl?: number
): AsyncState<T> {
const store = useContext(DataContext)
const init = store.has(key) ? finished<T>(store.get(key)) : null
const [state, actions] = useAsyncState<T>(init)

useLayoutEffect(() => {
if (store.has(key)) {
if (!ttl) store.delete(key)

return
}

let isCancelled = false
function finish(result: T) {
if (isCancelled) return

actions.finish(result)

if (!(result instanceof Error)) {
store.set(key, result, ttl)
}
}

function cleanup() {
isCancelled = true
}

actions.start()
Promise.resolve()
.then(() => fetcher(key, CONTEXT))
.then(finish, finish)

return cleanup
}, [store, key, actions, fetcher])

return state
.then(() => fetcher(key, { isServer: true }))
.then((result) => {
store.set(key, result)
store.setTTL(key, ttl)
})
}

export const useFetchData = IS_SERVER ? useFetchServerData : useFetchClientData

0 comments on commit ede7b6a

Please sign in to comment.