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

FR: allow to specify different fragments for list and detail view #40

Closed
macrozone opened this issue Sep 23, 2020 · 5 comments · Fixed by #53
Closed

FR: allow to specify different fragments for list and detail view #40

macrozone opened this issue Sep 23, 2020 · 5 comments · Fixed by #53
Labels

Comments

@macrozone
Copy link
Member

with the option resourceViews you can specify a custom fragment that will be used to fetch records. But it will be used for both list and detail view. it would be nice if you could specify a different fragment for detail and list view.

Proposal:

the fragment option for resource view could either be one fragment or an object with

{
   one: gql`... `
   many: gql`... `
}

i would use one and many because it aligns with the internal fetch types of react admin

@macrozone
Copy link
Member Author

🎉 This issue has been resolved in version 4.3.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@sMteX
Copy link
Contributor

sMteX commented Feb 15, 2021

@macrozone

Ok, so this is my fault for not testing this feature thoroughly and I am very sorry. I mentioned in my PR that I haven't actually tested this functionality in actual <Show> and <List> components (as I assumed it will work out of the box as the dataProvider calls worked) and I absolutely should have.

After playing with it in an actual application, it turns out there's a big catch in using it. React Admin uses optimistic rendering by default, i.e. it automatically caches all responses from dataProvider in order to save traffic and time. (That's also why they somewhat require the same shape of response for getOne and getMany)

So if we use different fragment for list and detail for example, what happens is this:

  1. We navigate to /Resource, RA renders an empty list
  2. RA calls dataProvider with the resource and many fragment and returns the result (which should contain id so RA can work with it)
  3. RA then caches the result into Redux store and re-renders the list
  4. If we click on the resource (showing the detail), RA looks into the Redux store for a resource with given id and immediately renders the detail with that info
  5. At the same time, it calls dataProvider.getOne() and uses the actual one fragment, and saves the response again in Redux store
  6. Re-renders the result from Redux store

Practically it means, that we have to treat every field that isn't common among any used fragments (since we can have multiple resource views on the same resource, and even those can be split into one and many) as possibly undefined as it will be like that until an actual response is returned, the Redux cache is updated and the view is re-rendered.

Technically it still works as it should but I think it at least needs a mention in the README. Or do you think we should revert the change as it technically worked with a single fragment (as the cached response would be the same shape whether for getOne or getMany)?

@macrozone
Copy link
Member Author

@macrozone

Ok, so this is my fault for not testing this feature thoroughly and I am very sorry. I mentioned in my PR that I haven't actually tested this functionality in actual <Show> and <List> components (as I assumed it will work out of the box as the dataProvider calls worked) and I absolutely should have.

After playing with it in an actual application, it turns out there's a big catch in using it. React Admin uses optimistic rendering by default, i.e. it automatically caches all responses from dataProvider in order to save traffic and time. (That's also why they somewhat require the same shape of response for getOne and getMany)

So if we use different fragment for list and detail for example, what happens is this:

  1. We navigate to /Resource, RA renders an empty list
  2. RA calls dataProvider with the resource and many fragment and returns the result (which should contain id so RA can work with it)
  3. RA then caches the result into Redux store and re-renders the list
  4. If we click on the resource (showing the detail), RA looks into the Redux store for a resource with given id and immediately renders the detail with that info
  5. At the same time, it calls dataProvider.getOne() and uses the actual one fragment, and saves the response again in Redux store
  6. Re-renders the result from Redux store

Practically it means, that we have to treat every field that isn't common among any used fragments (since we can have multiple resource views on the same resource, and even those can be split into one and many) as possibly undefined as it will be like that until an actual response is returned, the Redux cache is updated and the view is re-rendered.

Technically it still works as it should but I think it at least needs a mention in the README. Or do you think we should revert the change as it technically worked with a single fragment (as the cached response would be the same shape whether for getOne or getMany)?

no worries! thank you for the details analysis.

I would not revert it right now, but some thoughts and questions:

  • it works correctly, at least? just that the detail view is shown with partial data? Maybe we can disable this partial loading (or raise an Feature request on react-admin for such a flag)
  • what happens if you go back to the list? will it replace the detail data?
  • we also have an additional apollo-client cache, so switching between list and detail should not take much time once both has loaded once

since we can have multiple resource views on the same resource

they have then a different name, right? if i remember correctly, we treat these "virtual" resource views like different resources, so they appear different for react-admin and therefore do not share the same place in the redux store. So the problem only refers to list and detail view, i guess

@sMteX
Copy link
Contributor

sMteX commented Feb 16, 2021

From a light digging through their issues (here and here) it doesn't look like we can disable this caching, they don't want to support it as it's a design decision and frankly I am a little afraid of this particular sentence

Any other behavior of the dataProvider isn't supported, as it may impact several parts of the code that assume resources are shared.

I'm afraid that I'm just missing some behavior where this breaks again as I'm not very familiar with the insides of the framework (been only working with it for about a month and a half).

To answer your questions:

  • Switching between detail and list generally shows you the previous cached result (so going from detail to list, will first show you the detail version, and after list version is fetched, it updates it (and vice versa)).
  • Interesting thing I noticed, if List shows a subset of fields in Detail, then if you switch Detail->List, it directly uses the cached version and doesn't even re-render (though it does fetch it from API so I'm guessing it compares the response with cache and if nothing changes, it doesn't even trigger a re-render).
  • The caching seems to happen when we are using either hooks (tested with useGetList and useGetOne) or the <Show> and <List> components.
    const { data: one } = useGetOne('ResourceVirtual', 1)
    const { data: many } = useGetList('ResourceVirtual', pagination, sort, filter)
    In order to improve user experience (and not have loadings), it shows what's in the cache while it's waiting for a real response. So if the next view is showing more than the previous one, it will have those fields missing (though you can detect that situation in code and just show <CircularProgress /> or something).
  • It doesn't seem to happen when directly using dataProvider (or I'm just testing it incorrectly)
    const dp = useDataProvider()
    dp.getList('StockInVirtual', { pagination, sort, filter })
        .then(({ data: list }) => console.log({list}))
    dp.getOne('StockInVirtual', { id: 1})
        .then(({ data: one }) => console.log({one}))
  • I tried creating 2 resource views for the same resource, all having different fields and just logging what records are in <Show> and <List>. Looks like it really is cached by resource name, so 2 different resource views aren't shared, nor is it shared with the original resource.
  • Yes, I wasn't complaining about it being slow, it is generally pretty fast (sometimes it takes longer but I suspect that is just my snail machine)

@macrozone
Copy link
Member Author

🎉 This issue has been resolved in version 6.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants