Specify custom dependencies for `useObservableSuspense`
jesster2k10 opened this issue · 7 comments
I wrote this custom hook that uses the useObervableSuspense
to retrieve an Observable<T>
value from a custom AnyRepository class (implementation not shown) based on an id string.
export function useFindSuspense(name: RepositoryName, id: string) {
const repository = useAnyRepository(name);
const find$ = repository.findById(id);
const resource = new ObservableResource(find$, value => !!value);
return useObservableSuspense(resource);
}
The value of the ID is gotten from the react-router
(v6) useParams()
hook like so:
import {useParams} from 'react-router';
const DetailComponent = () => {
const deck = useFindSuspense('decks', deckId);
// render component
};
const Page = () => {
const {deckId} = useParams()
// this is wrapped in a Suspense component
return <DetailComponent deckId={deckId} />
}
and is passed to the custom useFindSuspense
hook.
Currently, when the route is changed, the useObservableSuspense
hook returns the same value.
This is despite the underlying Observable<T>
and find$
changing and also the id
/deckId
input.
I've tried to the following:
- Memoize the Observable Resource
export function useFindSuspense(name: RepositoryName, id: string) {
const repository = useAnyRepository(name);
const resource = useMemo(() => {
const find$ = repository.findById(id);
const observableResource = new ObservableResource(find$, value => !!value);
return observableResource;
}, [id, repository]);
return useObservableSuspense(resource);
}
Note: Adding this line of code:
useEffect(() => {
console.log('Changed');
}, [resource]);
prints the Changed to the console as expected (on route change) but the value returned from the hook and the useObservableSuspense
hook is unchanged.
- Call
useEffect
to manually update the resource
export function useFindSuspense(name: RepositoryName, id: string) {
const repository = useAnyRepository(name);
const find$ = repository.findById(id);
const resource = new ObservableResource(find$, value => !!value);
useEffect(() => {
resource.reload(repository.findById(id));
}, [id]);
return useObservableSuspense(resource);
}
Both of which did not do anything, even when the ObservableResource itself changed and it's inputs have changed.
So I assumed that this was because there's no way to specify custom dependencies for the useObservableSuspense
hook, which could be "re-calculated" if those dependencies changed.
I will go and try patch the hook to get a working solution, but I think that is what might be nescessary.
Looking through the source code, I have a feeling that this boils down to
which leads to
and in that useEffect
or useCustomEffect
hook, only the first parameter is being called upon as a dependency, excluding any external arguments like the id:string
in this case.
Although, since it registers changes with regards to the input$ observable, I don't see why it's not updating.
Actually to add to that,
args
param itself which could explain why this issue is arising.What actually is happening is that on this line here:
the args value is not being passed as a dependency to the
useIsomorphicLayoutEffect
hook which means no further updates are registered (or what I assume)After adding the following into the useObservableSuspense
hook everything works as expected:
useEffect(() => {
setState(resource.read());
}, [resource]);
I'm not sure if there are any implications elsewhere of this code, but so far that has solved the issue for me. I updated it on my fork there. Awaiting comment
So I assumed that this was because there's no way to specify custom dependencies for the useObservableSuspense hook, which could be "re-calculated" if those dependencies changed.
If you want to listen to props or states changes, there is a useObservable
hook in observable-hooks which is for converting dependencies into an observable.
If you can make a codesandbox repro etc I am happy to help debug the issue.
Here's a sandbox showing the issue at hand. I have another one (which I'll setup) where I was able to fix the issue by adding in that useEffect hook.
This is how I have my code setup basically. I could be misusing the library, although I do think this usage is pretty standard.
https://codesandbox.io/s/determined-germain-rlvut?file=/src/App.tsx
https://rlvut.csb.app
Thanks for the example. I can see a few issues in the code:
- The
useFindPostSuspense
callsrepo.findById
andnew ObservableResource
on every rendering. So everytime a new resource is generated. useFindPostSuspense
creates and consumes a suspense resource in the same component. In React the component which is under suspense will be destroyed and an alternative component will be rendered. It is uncommon that a component suspenses itself to wait for itself.
I think the cause of the issue is that you were trying to use ObservableResource
as a hook which it isn't (hooks generally starts with 'use').
An ObservableResource
is designed to match only one observable. Technically it is possible to support switching observable source, but I think it's unnecessary since RxJS operators are just more flexible. You can do all kinds of operations over multiple observables like merge or concat.