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

Dependant resources #46

Open
makenosound opened this issue Oct 1, 2020 · 6 comments
Open

Dependant resources #46

makenosound opened this issue Oct 1, 2020 · 6 comments

Comments

@makenosound
Copy link

makenosound commented Oct 1, 2020

Having used this a little more recently, one of the things that I’ve come across a few times is that I’d like to have resources that are dependent on the result of another resource.

Something like:

import { createResource, useResource } from "react-resource-router"
import accountResource from "./resources"

export const dependentOnAccountResource = createResource({
  type: "DEPENDENT",
  getKey: () => "dependent",
  resources: [accountResource],
  getData: async => {
    const [{ data }] = useResource(accountResource)
    return await fetch(`/feature_dependent_on_account_config/${data.account.id}`)
  },
})

This exact structure will fail obviously (not least because data will be null) but I wondered if it was something that felt possible/useful in the scope of this library. The reason I like something like it is that means:

  • Interfaces for fetching data can be consistent
  • Avoids having to do caching for dependent structures within backend infra

Would be interested to know if you’d felt similar pains, and if you’d solved them in other ways (or if I’ve missed something that means this is already easy). Thanks!

@theKashey
Copy link
Collaborator

That's a good pattern, I am using it one project:

  • one resource is loading a product
  • another some extra data for the product, and needs information for the product to perform a search

In my case it is resolved via declarative dependencies of a resource, something like resources in your case, but I am combining all dependencies, so every function does a single operation, which is a little more predictable.

export const dependentOnAccountResource = createResource({
  type: "DEPENDENT",
  getKey: () => "dependent",
- resources: [accountResource],
+ dependencies: [
+  /*resource*/accountResource,  
+  /*url param*/'queryParamName',
+  /*random selector*/(routerContext) => routerContext.params.paramName
+ ],
  
  getData: async (accountResourceResponse, queryParam, param) => {
-   const [{ data }] = useResource(accountResource); // useResource is a react hook, it's would be very hacky to use it here
    return await fetch(`/feature_dependent_on_account_config/${accountResourceResponse.account.id}`)
  },
})

However, that would be a breaking change for RRR, so - probably not the best idea. I hope there are better way to compose not depend.

@makenosound
Copy link
Author

@theKashey Yes, defined wasn’t suggesting we actually use a hook within the resource body! I’d be interested to see more about how your declarative example works in practice.

Thinking about this a little more and it would relatively easy to compose a fetcher that was memoized using the value from the route params, which would mean you could create an implicit relationship between two resources by sharing that fetcher. It feels to me like it would be a nice additional to have something out of the box that allowed that sort of behaviour, even if it were a little manual (passing the router context around).

@makenosound
Copy link
Author

Oh, and I realise that it would be a lot more difficult to do the sort of composition outside the library in the case of trying to support derived state from a call to an update method.

@theKashey
Copy link
Collaborator

theKashey commented Oct 2, 2020

Long story short - to fullfill your request we have to ensure only two moments:

  • side-effect(getData) is not called when it shall not be called. For example, it expected not to be called via some "guard" mechanics (call fetch only for users with even account-id, or call only when feature flag is on). Another example is not calling it when it's awaiting for some resource.
  • side-effect is not called more than it should. That means - only when call arguments are changed.

Nowadays the second is controlled via getKey, while the first one is not really present. To make it work one have to introduce more separation, and probably remove getKey as long as key is not call params, while only call params matters.

This is actually a long story, for example for useEffect - could it list less dependencies than it uses (and being called less often)? could it list more (and be called more often)?

The final API might look as following:

export const dependentOnAccountResource = createResource({
  type: "DEPENDENT",
- getKey: () => "dependent",
- resources: [accountResource],
+ dependencies: [
+  /*resource*/accountResource,  
+  /*url param*/'queryParamName',
+  /*random selector*/(routerContext) => routerContext.params.paramName
+ ],
+ mapDependenciesToState:([accountResourceResponse, queryParam, param], routerState) => (
+    // using well known name for example
+   return `/feature_dependent_on_account_config/${accountResourceResponse.account.id}`  
+ }
+ guard: (state, resources, routerState) => routerState.featureFlags.enabled && accountResourceResponse.accoind.id%2 === 0, 
// as I've said -> only even accounts (or it's only odd?)
+  
  getData: async (state) => {
    // no logic here. This is an executor or a changed state
    return await fetch(state)
  },
})

An open question is the location of a guard - I would personally prefer to have it separately (easier to test), but it can live in getData - you will just expose more information (like resources) to getData which it's like should not know about.

@makenosound
Copy link
Author

Yep, right, that makes sense to me. Thanks for outlining it in a bit more detail!

@talbet
Copy link

talbet commented Aug 11, 2021

Did anything like this ever get merged into react-resource-router? I know this issue was raised a long time ago, but I have run into a similar use case. It would be great if the current state of other resources was exposed somewhere, although I know this increases complexity dramatically.

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