Closed LiteSoul closed 5 years ago
You're correct, the hooks purely handle listening to the firebase DB.
You don't need hooks to be able to send or update data, you can simply use the standard Firebase APIs, e.g.
const Component = () => {
const ref = firebase.database().ref('/example');
const { loading, value } = useObjectVal(ref);
const setValue = (v) => ref.update(v);
return (
<div>
<div>Current value: {value}</div>
<button onClick={() => setValue('Changed')}>Update</button>
</div>
);
}
I can't use const { loading, value } = useObjectVal(ref);
Argument of type 'DocumentReference' is not assignable to parameter of type 'Query'.
Here's something that uses a variation of the pattern presented in this library. See useAsync
at the bottom.
useAsync.ts
/**
* Similar to `useLoadingValue.ts` (see link), but loading isn't true by default when value is `undefined`
* https://github.com/CSFrequency/react-firebase-hooks/blob/7eb49f1624d7c1bfb5ad8083a8702b19bf0e6929/util/useLoadingValue.ts
*/
import { useReducer, useCallback } from 'react'
export type LoadableHook<V, T, E> = [
{ value?: V; executor: (arg: T) => void; reset: () => void },
boolean,
E | undefined
]
export type LoadableValue<T, E> = {
error?: E
loading: boolean
reset: () => void
setLoading: (loading: boolean) => void
setError: (error: E) => void
setValue: (value?: T | null) => void
value?: T
}
type ReducerState<E> = {
error?: E
loading: boolean
value?: any
}
type ErrorAction<E> = { type: 'error'; error: E }
type ResetAction = { type: 'reset'; defaultValue?: any }
type ValueAction = { type: 'value'; value: any }
type LoadingAction = { type: 'loading'; loading: boolean }
type ReducerAction<E> =
| ErrorAction<E>
| ResetAction
| ValueAction
| LoadingAction
const defaultState = (defaultValue?: any) => {
return {
loading: false,
value: defaultValue,
}
}
const reducer = <E>() => (
state: ReducerState<E>,
action: ReducerAction<E>
): ReducerState<E> => {
switch (action.type) {
case 'loading':
return {
...state,
loading: action.loading,
}
case 'error':
return {
...state,
error: action.error,
loading: false,
value: undefined,
}
case 'reset':
return defaultState(action.defaultValue)
case 'value':
return {
...state,
error: undefined,
loading: false,
value: action.value,
}
default:
return state
}
}
export const useLoadableValue = <T, E>(
getDefaultValue?: () => T | null
): LoadableValue<T, E> => {
const defaultValue = getDefaultValue ? getDefaultValue() : undefined
const [state, dispatch] = useReducer(reducer<E>(), defaultState(defaultValue))
const reset = () => {
const defaultValue = getDefaultValue ? getDefaultValue() : undefined
dispatch({ type: 'reset', defaultValue })
}
const setLoading = (loading: boolean) => {
dispatch({ type: 'loading', loading })
}
const setError = (error: E) => {
dispatch({ type: 'error', error })
}
const setValue = (value?: T | null) => {
dispatch({ type: 'value', value })
}
return {
error: state.error,
loading: state.loading,
reset,
setLoading,
setError,
setValue,
value: state.value,
}
}
export type AsyncHookNoValue<T> = [(arg: T) => void, boolean, Error | undefined]
export type AsyncHook<V, T> = [
{ value?: V; executor: (arg: T) => void; reset?: () => void },
boolean,
Error | undefined
]
export const useAsync = <V, T>(
asyncFunction: (arg: T) => Promise<V>
): LoadableHook<V, T, Error> => {
const {
value,
loading,
error,
reset,
setLoading,
setError,
setValue,
} = useLoadableValue<V, Error>()
const executor = useCallback(
async (arg: T) => {
setLoading(true)
try {
const value = (await asyncFunction(arg)) as V
setValue(value)
setLoading(false)
} catch (error) {
setError(error)
}
},
[asyncFunction] // eslint-disable-line react-hooks/exhaustive-deps
)
return [{ value, executor, reset }, loading, error]
}
And then here's how you use it:
createData.ts
const getDataCreator = () => {
const firebaseContext = useContext(FirebaseContext) // eslint-disable-line react-hooks/rules-of-hooks
if (firebaseContext) {
const dataCollection = firebaseContext.firestore().collection('some-collection')
return (title: string) =>
dataCollection!?.add({
title,
createdDate: firebase.firestore.FieldValue.serverTimestamp(),
})
}
}
export const useDataCreator = (): AsyncHook<
firebase.firestore.DocumentReference<firebase.firestore.DocumentData>,
string
> => {
const createData = getDataCreator()
let [{ value, executor, reset }, isLoading, error] = useAsync(createData)
if (error) {
logger.error(error)
error = Error('Unable to create data')
}
return [{ value, executor, reset }, isLoading, error]
}
Unless I'm missing something, besides listening to the firebase DB, how can we send or update data to it with some custom hook?