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

useFetcher getter class inheritance issues #458

Closed
Jsarihan opened this issue Jan 5, 2021 · 11 comments
Closed

useFetcher getter class inheritance issues #458

Jsarihan opened this issue Jan 5, 2021 · 11 comments
Labels
bug Something isn't working

Comments

@Jsarihan
Copy link

Jsarihan commented Jan 5, 2021

React version
16.13.1

Concurrent mode
No

Rest Hooks version
4.5.5

Describe the bug
Getter inheritance not working as expected with useFetcher. e.g. given a class
ResourceA and a subclass ResourceB extends ResourceA. The object returned by useFetcher(ResourceB.someShape()) is missing getters from ResourceA. This is not an issue with useResource.

To Reproduce
Steps to reproduce the behavior:

  1. Create a class
class ResourceA extends SimpleResource {
  readonly active!: boolean;
  ...
  get can_delete() {
    return !this.active;
 }
}
  1. Create a subclass
class ResourceB extends ResourceA {
 ...
 // Other properties
}
  1. Call useFetcher on resourceB (either promise or async/await, neither work)
const getPosts = useFetcher(ResourceB.listShape());
...
const posts = await getPosts({id: USER_ID});
const deletablePosts = posts.filter((post) => post.can_delete);

Deletable posts is empty regardless of the value of this.active. The behavior works as expected when doing

const posts = useResource(ResourceB.listShape(), {id: USER_ID});
const deletablePosts = posts.filter((post) => post.can_delete);

Expected behavior
I would expect class inheritance of getters to be consistent across useResource and useFetcher. Ideally, subclasses in both cases would have access to the parent getter.

Additional context
Manually setting the object prototype via setPrototypeOf does not work either. Intellisense/TS has no complaints about calling the getters.

ES6 context:
https://stackoverflow.com/questions/53584705/javascript-extending-es6-class-setter-will-inheriting-getter

TS context:
microsoft/TypeScript#338
microsoft/TypeScript#25927

Thanks!

@Jsarihan Jsarihan added the bug Something isn't working label Jan 5, 2021
@ntucker
Copy link
Collaborator

ntucker commented Jan 5, 2021

I believe this may actually be returning a POJO than a class. Can you verify if it can even use getters in the descendant class from useFetcher()?

Changing this to return the class is planned for v5, which will also guarantee referential equality.

@Jsarihan
Copy link
Author

Jsarihan commented Jan 6, 2021

You're right. Looks like a POJO, all getters are undefined. Thanks for the context

Is this included in the current v5 beta version?

@ntucker
Copy link
Collaborator

ntucker commented Jan 6, 2021

Is this included in the current v5 beta version?

Not yet. As a workaround, you could do the following for now (assumes running v5 beta):

import { normalize, denormalize } from '@rest-hooks/normalizr';
import { useFetcher } from 'rest-hooks';

function useBetterFetcher<
  Shape extends FetchShape<Schema, Readonly<object>, any>
>(
  endpoint: Shape,
  throttle = false,
) {
  const fetch = useFetcher(endpoint, throttle);
  return useCallback(async (...args: Parameters<fetch>) => {
    const res = await fetch(...args);
    const { entities, result } = normalize(res);
    return denormalize(result, endpoint.schema, entities);
  }, [ fetch ]);
}

@Jsarihan
Copy link
Author

Jsarihan commented Jan 6, 2021

Will give that a shot, thank you!

@ntucker
Copy link
Collaborator

ntucker commented Jan 13, 2021

Open question

Say a fetch returns an entity that is already in store. In some cases (due to race conditions) you might want to (use the existing entity, because it's actually newer](https://resthooks.io/docs/next/api/SimpleRecord#static-merget-extends-typeof-simplerecordfirst-instancetypet-second-instancetypet--instancetypet) (which can be determined by a timestamp). This already affected what is returned by useResource() or useCache(). However, should it also affect the return value of a fetch?

That is to say, should we

  1. Ensure we have the latest state of the consistent cache at the time of resolution
  2. Reflect the actual return value from the given fetch

Given that imperative fetches can have side effects, it's not clear which would be better. Should we maybe then provide both? or maybe a different hook for each case?

@yunyu
Copy link
Contributor

yunyu commented Jan 22, 2021

@ntucker I'm running into a variant of this issue as well, and my vote is for 2). My rationale is:

  • For side-effect free useFetcher calls, the return value is almost always ignored. useFetcher is only used to force trigger an update, and useResource is used for display logic (which is always updated with the latest version from the patch).
  • For effectful useFetcher calls, there may be additional data from the call (if you consider an exception as a viable return value) that are not available in the raw useResource. It makes sense to return the denormalized version of the return value in this situation.

@ntucker
Copy link
Collaborator

ntucker commented Jan 22, 2021

@yunyu I'm a bit confused, because you talk about denormalized version (which is what useResource() returns - includes class properties, etc). but then say #2 - which returns the raw fetch response (which won't have classes). Both can have any additional data from the fetch - just one is processed through the cache and the other is the input to that processing.

@yunyu
Copy link
Contributor

yunyu commented Jan 22, 2021

@ntucker Sorry, I realized that I was advocating for a 3rd solution (return a denormalized version of the result from the fetch). What I meant to say is that IMO, the return value of useFetcher should represent the entity at the time when the fetch call succeeds and not subsequent updates.

@yunyu
Copy link
Contributor

yunyu commented Feb 9, 2021

There's another context in which this behavior poses a type safety issue. If you fetch a resource with a defined schema field (let's say nestedFoo), the return value of fetch will have a schema field of string when it is actually an object. I think that denormalizing by default makes the most sense here (aka the useBetterFetcher behavior above)

@ntucker
Copy link
Collaborator

ntucker commented Apr 17, 2021

Tracking solution in #760

@ntucker ntucker closed this as completed Apr 17, 2021
@ntucker
Copy link
Collaborator

ntucker commented May 17, 2024

Resolution from useFetch and ctrl.fetch() now resolves to a denormalized form (tho does not incorporate any existing entities from state).

This was first introduced in rest hooks 8. Though not all schemas work perfectly until data client 0.11

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants