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

Profile server client refresh #420

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from

Conversation

bitbeckers
Copy link
Contributor

@bitbeckers bitbeckers commented Feb 7, 2025

Refactors the data fetching of the hypercert profile tabs to user server side APIs. Also implements a path invalidation flow that clears cached calls when a fraction claim was succesfully executed

Refactors and optimises the data fetching of the tabs pages to speed up
the loading of the data
@bitbeckers bitbeckers added the bug Something isn't working label Feb 7, 2025
@bitbeckers bitbeckers requested a review from Jipperism February 7, 2025 15:43
@bitbeckers bitbeckers self-assigned this Feb 7, 2025
Copy link

vercel bot commented Feb 7, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
hypercerts-app ❌ Failed (Inspect) Feb 17, 2025 7:20pm

This was referenced Feb 7, 2025
Base automatically changed from feat/batch-claim-fraction to dev February 7, 2025 18:10
typo in filename, console logs, redundant business logic
@bitbeckers bitbeckers changed the title WIP: profile server client refresh Profile server client refresh Feb 10, 2025
@bitbeckers bitbeckers marked this pull request as ready for review February 10, 2025 09:28
Copy link
Contributor

@Jipperism Jipperism left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm struggling a bit to come to grips with how all of this works. I understand we're polling from the client side now, but I'm not sure how the api routes get invalidated as they will be caching results as well. Calling revalidateServerPath("profile/[address]") will invalidate the cached data for all server components. I take it that it will invalidate all api's under that "profile/address" path as well? Because that data is now not server-component bound anymore but being fetched using react-query.

Another question: how does this data get invalidated if the user closes their window before the transaction completes? Are we then stuck with stale data until the next invalidation due to interaction (or vercel deploy)?

return { ...fraction, metadata: metadata?.data ?? null };
}),
);
claimable.data = processedData;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reassigning this is a bit unidiomatic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean? should it be named differently? Suggestions?

Copy link
Contributor

@Jipperism Jipperism Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean doing something more like { ...claimedable, data: processedData }, so we avoid mutating values. I think in this case the chances of errors popping up due to this reassignment are minimal tho, as it's a lambda function that doesn't share references with anything else. To be fair, it's a bit of a nitpick but I usually try to avoid doing this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you ok with it being a temporary implementation untill we have hypercerts-org/hypercerts-api#240

import { useQuery } from "@tanstack/react-query";

export const ClaimableContent = ({ address }: { address: string }) => {
const { data: response, isLoading } = useQuery({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to see this split out to a separate file, so that revalidation issues are fixed everywhere automatically once this gets used in a different plaec as well. That would also allow for specifying the return type in the hook instead of having to cast the data type after.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typecasting is only temporary untill we have this fixed in the API. Basically you can't get allow list records with metadata at the moment, hence the attaching of metadata in the API endpoint as well.

The query key is specific to this file, so why would we split out the hook that's only used in one place?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's currently also used to get the tab counts? Granted, due to the query key being equal the call will only be done once, and both these components will just pull the results from the query cache.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved into hooks and pushed

});

const { data: owned } = useQuery({
queryKey: ["hypercerts-owned", address.toLowerCase()],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good example of where we would benefit from splitting out those hooks to their own files. The key differs between this one and the one used in hypercerts-tab-content-owned ("hypercerts-owned" vs "owned-hypercerts") which means that

  • invalidating won't updated the hypercerts everywhere
  • we're fetching twice (and polling) for the same exact data instead of reusing
  • more surface area for typing errors

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, but this call is actually painfully inefficient for only getting the call. Wouldn't it be better to just call for the count?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing a call just for the count would also work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the same hooks as for the content components untill this is resolved: hypercerts-org/hypercerts-api#255

Added a TODO. It would be a nice to have, so we can progressively render the tab content, skeletons and actual data

queryKey: ["hypercerts-data", address.toLowerCase()],
});
await revalidatePathServerAction(`/profile/${address}`);
router.refresh();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to refresh? Seems to me that invalidating the queries should be enough to trigger a new fetch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't tell you this at the moment as the API isn't invalidating the data that it should. The refresh was already in the batch claim button and I just made them consistent.

Also, according to the docs invalidating dynamic paths doesn't apply without refetching: https://nextjs.org/docs/14/app/api-reference/functions/revalidatePath

`/profile/${account.address}?tab=hypercerts-claimable`,
`/profile/${account.address}?tab=hypercerts-owned`,
]);
await refreshData(getAddress(account.address!));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like we are not invalidating /hypercerts/${selectedHypercert?.hypercert_id} anymore now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I read the correctly, we should precisely specify all API call paths: https://nextjs.org/docs/14/app/api-reference/functions/revalidatePath

correct?

Copy link
Contributor

@Jipperism Jipperism Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends on what you mean by API call path. As I understand it, the paths you specify here are supposed to be frontend paths, and not api paths. The server components that use fetch to get data before being rendered that are in those frontend paths will then have their cache purged, which results in them refetching data when refreshed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the invalidation methods to match the profile, api and hypcert view pages according to the doc. Again, a bit pending on this one to be completed to validate: hypercerts-org/hypercerts-api#254

Replace useQuery implementation in hypercerts-tab-content and -counts
component with hooks for reusability
Removes unused hooks and services from the hypercerts folder
Updates the refreshData methods in the unclaimed hypercerts claim and
batch claim button to:

- invalidate the user's profile page
- invalidate the relevant API paths
- all hypercert view pages for the claimed fractions hypercert IDs
- Refactors the refactor into server side data fetching, aiming for as
much SSR as possible
- Moves actions for hypercerts and allowlist into `actions` folder to
make their purpose explicit
- Removes the suspense boundary on the hypercerts-tab-content component
and puts it on the highest level possible - the UnclaimedFractions table
- Adds allowlist action for fetching unclaimed fractions with metadata
attached

return <UnclaimedHypercertsList unclaimedHypercerts={data} />;
};
return <UnclaimedHypercertsList unclaimedHypercerts={[...data]} />;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for ...spreading the results here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The action returns a readonly object and spreading creates a now non-readonly array

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be too nitpicky but do we know why/where it's marked as readonly? Because this just created an new array with references to the same objects as in the original data, which end up being mutable (which we shouldn't do anyways).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because that's what the readFragment method returns. It ensures typesafety and data consistency imho, we can view our Graph as the SoT and mutating API responses should be explicit. At least, that's how I interpret it.

The cleaner option could be to expect an readonly array in the component. Which I think is valid but we should apply in more places then

- removes the delete action from the metadata fetcher. The requestmap is
ephemeral so we let is run with the lifecycle of the method
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
Status: In review
Development

Successfully merging this pull request may close these issues.

2 participants