Simple usage in hooks
Hello,
I would like your advice on how to use WDB from hooks. I know queries hooks are not supported right now, but I have seen examples on the web that I can't find again. I guess it would be nice to add them as temporary solutions somewhere.
I would like to know if there is a way to simplify the following:
Subscribe to a query
// Component.tsx
const clause = useMemo(() => [Q.where('some_field', someValue)], [someValue])
const posts = useDbQuery('posts', clause)
// Hooks.ts
export function useDbQuery<T extends Model>(tableName: string, clause: Clause[]): T[] {
const database = useDatabase()
return useObservable<T[], [Clause[]]>((_, inputs$) =>
inputs$.pipe(
switchMap(([clause]) => database
.get<T>(tableName)
.query(...clause)
.observe()),
)
, [], [clause])
}
Query from a "has many" relationship
// Component.tsx
const clause = useMemo(() => [Q.where('some_field', someValue)], [someValue])
const comments = useExtendQuery(post.comments, clause)
// Hooks.ts
export function useExtendQuery<T extends Model>(query: Query<T>, clause: Clause[]): T[] {
return useObservable<T[], [Clause[]]>((_, inputs$) =>
inputs$.pipe(
switchMap(([clause]) => query
.extend(...clause)
.observe()),
)
, [], [clause])
}
I now have to add variants for observeWithColumns.
Thank you.
edit Version with columns from a relationship
export function useExtendQueryColumns<T extends Model>(query: Query<T>, clause: Clause[], observeColumns: string[]): T[] {
const inputs: [Clause[], string[]] = [clause, observeColumns]
return useObservable<T[], [Clause[], string[]]>((_, inputs$) =>
inputs$.pipe(
switchMap(([clause]) => query
.extend(...clause)
.observeWithColumns(observeColumns)),
)
, [], inputs)
}
This might help.
Hey thanks, this is kind of the same as what I am doing. I'll try if useObservableState can be better with what I do. The internal observable state seems to be cached forever when I change the code and the page reloads.
I hope official hooks can be made available soon.
@isaachinman I'd be interested in your criticism for those hooks.
The drawback is that I have to freeze some data outside of the hook like this:
const commentsFilter = useMemo(
() => [Q.where('step', stepIndex), Q.sortBy('name')],
[stepIndex],
)
const observeColums = useRef(['step', 'name'])
But on the other side I guess joining column names and json-stringifying the query clauses is very fast anyway.
But I am not sure what are the pro/cons over useObsevableState or useObservable.
But I am not sure what are the pro/cons over useObservableState or useObservable.
I am no rxjs expert. But my understanding is that useObservable returns a new observable, while useObservableState subscribes to an existing observable. The latter is preferable because Watermelon is already exposing observables. I found weird/buggy behaviour when trying to use useObservable, as you're essentially adding another layer of observable.
The drawback is that I have to freeze some data outside of the hook like this
Any useDbQuery type hook should take all dependencies/query data as arguments, and memoise within, in my opinion.
It's a shame we have not had a response from @radex, as I am sure we could easily put together a collaborative PR to introduce official hooks.
Any useDbQuery type hook should take all dependencies/query data as arguments, and memoise within, in my opinion.
Yes that would make it far easier to use, no need for the user to think about memoization.
Ok I looked at the code and indeed the observable you create seems to be properly unsubscribed from when a new observable is given to useObservableState. I'll try to replace my queries with your code.
Yeah, a library of hooks could be very useful. In the mean time if you have other hooks to share I'd be pretty happy. If you look at my first hook above I have to call useDatabase inside. Where does the database variable compe in your useDatabaseRows ?
The database variable is wherever you call new Database.
Oh so you define the hooks there so no need to useDatabase afterwards. Got it!
Another version to avoid JSON.stringify:
export function useExtendQueryColumns<T extends Model>(query: Query<T>, clause: Clause | Clause[], observeColumns: string[]): T[] {
const [observableColumnsMemo, clauseMemo] = useDeepMemo(
() => [observeColumns, arrayWrap(clause)],
[observeColumns, clause],
)
const observable = useMemo(
() => query
.extend(...clauseMemo)
.observeWithColumns(observableColumnsMemo)
,
([query, observableColumnsMemo, clauseMemo]),
)
return useObservableState(observable, [])
}
function arrayWrap<T>(itemOrList: T | T[]): T[] {
if (Array.isArray(itemOrList)) return itemOrList
return [itemOrList]
}
function useDeepMemo<T>(generator: () => T, dependencies: unknown[]): T {
const ref = useRef({ value: null, deps: [] }) as MutableRefObject<{ value: null | T, deps: unknown[] }>
if (!isEqual(ref.current.deps, dependencies)) {
ref.current = { value: generator(), deps: dependencies }
}
return ref.current.value as T
}
Not sure how to benchmark that though.