Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple usage in hooks #1848

Open
lud opened this issue Oct 23, 2024 · 8 comments
Open

Simple usage in hooks #1848

lud opened this issue Oct 23, 2024 · 8 comments

Comments

@lud
Copy link

lud commented Oct 23, 2024

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)
}
@heliocosta-dev
Copy link
Contributor

This might help.

@lud
Copy link
Author

lud commented Oct 26, 2024

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.

@lud
Copy link
Author

lud commented Oct 26, 2024

@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.

@isaachinman
Copy link

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.

@lud
Copy link
Author

lud commented Oct 26, 2024

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 ?

@isaachinman
Copy link

The database variable is wherever you call new Database.

@lud
Copy link
Author

lud commented Oct 26, 2024

Oh so you define the hooks there so no need to useDatabase afterwards. Got it!

@lud
Copy link
Author

lud commented Oct 26, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants