Skip to content

Instantly share code, notes, and snippets.

@jenya239
Last active August 6, 2021 08:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jenya239/1be38a7c7beb9f74cbb8186f00cc6132 to your computer and use it in GitHub Desktop.
Save jenya239/1be38a7c7beb9f74cbb8186f00cc6132 to your computer and use it in GitHub Desktop.
import React, { Context, Reducer, useContext, useMemo, useReducer } from 'react'
type Action<Value> =
| { type: 'request' }
| { type: 'success'; value: Value }
| { type: 'failure'; error: string }
| { type: 'reset' }
type Dispatch<Value> = (action: Action<Value>) => void
type State<Value> =
| { status: 'notLoaded' }
| { status: 'loading'; value?: Value }
| { status: 'error'; error: string }
| { status: 'success'; value: Value }
const reducer = <Value extends unknown>(
state: State<Value>,
action: Action<Value>
): State<Value> => {
switch (action.type) {
case 'request':
return {
...state,
status: 'loading',
}
case 'success':
return {
status: 'success',
value: action.value,
}
case 'failure':
return {
status: 'error',
error: action.error,
}
case 'reset':
return {
status: 'notLoaded',
}
}
}
interface IActions {
reset: () => void
doFetching: () => Promise<void>
}
interface FetchableContextValueType<Value, Views> {
state: State<Value>
actions?: IActions
views?: Views
}
export const createContext = <Value, Views>(): Context<FetchableContextValueType<Value, Views>> =>
React.createContext<FetchableContextValueType<Value, Views>>({ state: { status: 'notLoaded' } })
export const useFecthableReducer = <Value extends unknown>(): [State<Value>, Dispatch<Value>] =>
useReducer<Reducer<State<Value>, Action<Value>>>(reducer, {
status: 'notLoaded',
})
export const useFecthableActions = <Value extends unknown>(
dispatch: Dispatch<Value>,
getFetchable?: () => Promise<Value>
): IActions | undefined =>
useMemo(
() =>
!getFetchable
? undefined
: {
reset: () => dispatch({ type: 'reset' }),
doFetching: async () => {
dispatch({ type: 'request' })
try {
dispatch({ type: 'success', value: await getFetchable() })
} catch (e) {
console.error(e)
dispatch({ type: 'failure', error: e })
}
},
},
[getFetchable]
)
type HookType<Value, Views> =
| { isFetchable: false; success: false }
| ({ isFetchable: true; success: false } & IActions & { status: 'notLoaded' })
| ({ isFetchable: true; success: false } & IActions & { status: 'error'; error: string })
| ({ isFetchable: true; success: false } & IActions & { status: 'loading' })
| ({ isFetchable: true; success: true } & IActions & { status: 'loading' } & {
value: Value
} & Views)
| ({ isFetchable: true; success: true } & IActions & { status: 'success' } & {
value: Value
} & Views)
export const useFetchableContext = <Value, Views>(
Context: Context<FetchableContextValueType<Value, Views>>,
name: string
): HookType<Value, Views> => {
const context = useContext(Context)
if (context === undefined) {
throw new Error(`useContext must be used within a ${name}Provider`)
}
if (!context.actions) {
return { isFetchable: false, success: false }
} else if (context.state.status === 'notLoaded') {
return { isFetchable: true, success: false, ...context.state, ...context.actions }
} else if (context.state.status === 'error') {
return { isFetchable: true, success: false, ...context.state, ...context.actions }
} else if (context.state.status === 'loading' && context.state.value === undefined) {
return {
isFetchable: true,
success: false,
status: 'loading',
...context.actions,
}
} else if (
context.state.status === 'loading' &&
context.state.value !== undefined &&
context.views
) {
return {
isFetchable: true,
success: true,
status: 'loading',
value: context.state.value,
...context.actions,
...context.views,
}
} else if (context.state.value && context.actions && context.views) {
return {
isFetchable: true,
success: true,
status: 'success',
value: context.state.value,
...context.actions,
...context.views,
}
}
throw new Error('Incorrect state')
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment