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

Binding to observables that emit SUSPENSE with a default value does not trigger React.Suspense to render the fallback component #307

Open
amydevs opened this issue Aug 10, 2023 · 1 comment

Comments

@amydevs
Copy link

amydevs commented Aug 10, 2023

Overview

I have a component that is using a returned hook from the bind function. Within the docs examples, they trigger a suspense component's fallback by starting with emitting the SUSPENSE symbol on the observable passed to the bind function. However, this does not work when I've passed a default value to bind.

The intention is to have a component that can have initial stale data that is to be hydrated from an observable

Recreation

I've first declared a signal and an asynchronous function:

   const [apiCall$, onApiCall] = reactRxjsUtils.createSignal();

    let i = 0;
    const f = (): Promise<number> => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(i++);
        }, 1000);
      });
    }

Here, I've followed the SUSPENSE page in the API docs in creating a hook, passing in an observable that should start with a value of suspense:

    const [useApiResult, apiResult$] = reactRxjsCore.bind(
      apiCall$.pipe(
        rxjsOperators.switchMap(
          () => concat(of(reactRxjsCore.SUSPENSE), from(f()))
        )
      ),
      null
    );

I've also tried this variation:

    const [useApiResult, apiResult$] = reactRxjsCore.bind(
      apiCall$.pipe(
        rxjsOperators.switchMap(
          () => from(f()).pipe(rxjsOperators.startWith(reactRxjsCore.SUSPENSE))
        )
      ),
      null
    );

And just only emitting SUSPENSE:

    const [useApiResult, apiResult$] = reactRxjsCore.bind(
      apiCall$.pipe(
        rxjsOperators.switchMap(
          () => of(reactRxjsCore.SUSPENSE)
        )
      ),
      null
    );

No matter whatever I do, except for removing the default null argument to bind, the fallback ("Loading...") is not shown when SUSPENSE is being emitted.

function Button() {
  const { onApiCall, useApiResult } = useService("test");
  const apiResult = useApiResult();
  React.useEffect(() => {
    if (apiResult == null) return;
    console.log(apiResult);
  }, [apiResult]);
  return (
    <button onClick={onApiCall}>Click me</button>
  );
}

function Test() {
  return 
  (
    <Subscribe fallback={"Loading..."}>
      <Button />
    </Subscribe>
  );
}
@CMCDragonkai
Copy link

CMCDragonkai commented Aug 10, 2023

This is useful because maybe you want to have a component that still works on the server side rendering, so you need a default value. On top of that, you need to be able to emit SUSPENSE on the observable so you can switch to loading fallback, or emit an error to trigger a error boundary when there's an error.

Basically it's a way of using an observable to reify the 4 states of an asynchronous operation state machine:

  1. Initial default state
  2. Loading state
  3. Success state
  4. Failure with error states

It seems that using a subject that comes out of createSignal, and composing it with a promise and binding it back into a component would be a useful way of doing this.

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

2 participants