crimx/observable-hooks

Make XHR requests using ObervableResource with a parameter

MaximeBernard opened this issue · 4 comments

Hi @crimx,
thanks a lot for this project!

I'm trying Render as you fetch suspense but I'm having issues with ObservableResource when I need to "create them inside components".

Basically I have

const r = new ObservableResource(ajax.get(API_URL));

but sometimes, I'm using routing which is mixed into the API_URL

const { id } = useParams();
const r = new ObservableResource(ajax.get(API_URL + '/' + id));

In this case:

  • if I create my ObservableResource inside the child component (where I useObservableSuspense(r), I get infinite loop (I think I get why)
  • if I create my ObservableResource inside the parent component and pass it to the child component (where I <Suspense...></Suspense>), I get 2 calls (I don't really understand why)

What do you suggest? I couldn't find any example using ajax from rxjs. Do you have any? What do you think overall about the approach?

Thank you for your time.

crimx commented

You cannot create Suspense resource inside Component that comsumes the resource. It will trigger the Suspense which destroys the Component and itself hence the infinite loop.

if I create my ObservableResource inside the parent component and pass it to the child component (where I <Suspense...>), I get 2 calls (I don't really understand why)

It was not supposed to. Do you have React StrictMode turned on? It will cause many unwanted calls.

It's hard to say without seeing the code.

I couldn't find any example using ajax from rxjs. Do you have any?

See the example:

import {
userResource,
postsResource,
userListResource,
fetchProfileData
} from './fakeApi'

It does not matter how you do ajax. Just export the API that will trigger ajax calls to the Component.

You cannot create Suspense resource inside Component that comsumes the resource. It will trigger the Suspense which destroys the Component and itself hence the infinite loop.

Makes more sense, thank you 👍

if I create my ObservableResource inside the parent component and pass it to the child component (where I <Suspense...>), I get 2 calls (I don't really understand why)

It was not supposed to. Do you have React StrictMode turned on? It will cause many unwanted calls.

YES! This was the reason. Removing StrictMode solves the issue. Explaination here
Thank you for pointing me in the right direction 👍

It does not matter how you do ajax. Just export the API that will trigger ajax calls to the Component.

I can't export the Observable like you do because I don't know the (REST) resource (/users/1 or /users/2?) until I'm in the component.

I could have gone both ways:

  1. Using a Subject and feeding the :id in the observable of the requested resource as soon as I have it
  2. Creating the observable on the fly in the parent component and passing it to the child component

I've chosen 2. because it's simpler and less verbose but I'm concerned about having to pass the "ObservableResource" to the child component. It doesn't seem right.

Right now, I'll probably create some HOC to encapsulate all that logic (Suspense + ObservableResource creation). Have you considered such a thing in observable-hooks?

Last question: have you considered creating a hook instead of calling new ObservableResource() directly? We'd probably benefit from useMemo?

crimx commented

I can't export the Observable like you do because I don't know the (REST) resource (/users/1 or /users/2?) until I'm in the component.

I am not exporting Observable either. Take a look at the example especially:

setUserId(id)
fetchProfileData(id)

setUserId(id) 
fetchProfileData(id)

I don't know the id until the exported API is called.

The idea is to turn API parameters to an Observale and then switchMap it to an Observable of ajax results.

Right now, I'll probably create some HOC to encapsulate all that logic (Suspense + ObservableResource creation). Have you considered such a thing in observable-hooks?

Last question: have you considered creating a hook instead of calling new ObservableResource() directly? We'd probably benefit from useMemo?

Creating resource inside the Component goes against the design of Suspense. I haven't came up with a good pattern for this.

The idea is to turn API parameters to an Observale and then switchMap it to an Observable of ajax results.

Yes, that's what I had in mind with Option 1. but it seems too verbose.

Creating resource inside the Component goes against the design of Suspense.

No I meant "creating resource in the parent component and pass it through props to the child component".

I haven't came up with a good pattern for this.

Yeah me neither. At the end so I gave up Suspense and chose to create my own hook:

const useAjaxState = <T>(url: string, initialState: T): T => {
  const ajax$ = useObservable(() =>
    ajax.get<T>(url).pipe(map((r) => r.response)),
  );
  return useObservableState(ajax$, initialState);
};

// usage
export const DisplayUser = (): ReactElement => {
  const users = useAjaxState<readonly User[]>(
    API_URL + '/users/1', 
    [],
  );

  return (
    <div>
      <h1>User {user.id}</h1>
      <pre>{JSON.stringify(user)}</pre>
    </div>
  );
};

Thank you again for your time.