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

fix: don't fail parse if called again with identical input #557

Merged
merged 6 commits into from
May 13, 2024

Conversation

rikbrown
Copy link
Contributor

@rikbrown rikbrown commented May 11, 2024

It should be possible to use nuqs to parse search params in multiple places in the same request (e.g. page + generateMetadata) - as long as the input is identical (which it should be).

This change stores the original searchparams object passed to parse alongside the parsed data. If parse is called again we check for referential equality and if identical we just return the already cached data.

note: that this will still fail if a different object with the same contents is passed. fortunately next.js uses the same object for search params in the same request so it solves the original use case. I didn't want to get more clever and do deeper comparison for simplicity/perf.

i stored the original object as a new field in the existing cache object (not exposed to users).

see #556

Testing

  1. I added some unit tests by lightly mocking React's cache. It's not a very clever mock but it works. If maintainer would rather not have me add these with this approach I can remove them.
  2. I also added a generateMetadata to the cache e2e test performing parse again (the fact it doesn't fail felt sufficient here, but I did also check the value of one of the params by setting the title).

Copy link

vercel bot commented May 11, 2024

@rikbrown is attempting to deploy a commit to the 47ng Team on Vercel.

A member of the Team first needs to authorize it.

@rikbrown rikbrown force-pushed the next branch 2 times, most recently from 593147e to 5c79fa3 Compare May 11, 2024 11:33
@rikbrown rikbrown marked this pull request as ready for review May 11, 2024 11:33
@rikbrown
Copy link
Contributor Author

Actually I just realised you have e2e tests setup, let me update them too.

@rikbrown
Copy link
Contributor Author

Actually I just realised you have e2e tests setup, let me update them too.

added a simple one

Copy link

vercel bot commented May 11, 2024

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

Name Status Preview Comments Updated (UTC)
nuqs ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 11, 2024 7:21pm

Copy link
Member

@franky47 franky47 left a comment

Choose a reason for hiding this comment

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

Thank you!

It looks good overall, apart from the storage of the input reference object (see comment).

I can't figure out how to approve CI to run on 3rd party PRs (the button was there this morning but gone now...), I'll see what I can do. Edit: note to self: this is to be done in the Actions, not on the PR itself. Re-edit: nope, still only applies to the first commit, not to subsequent (force-)pushes..

@@ -21,16 +21,33 @@ export function createSearchParamsCache<
// whereas a simple object would be bound to the lifecycle of the process,
// which may be reused between requests in a serverless environment
// (warm lambdas on Vercel or AWS).
const getCache = cache<() => Partial<ParsedSearchParams>>(() => ({}))
const getCache = cache<
() => Partial<ParsedSearchParams> & { _orig?: SearchParams }
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: Rather than storing the input in the same storage space as the rest of search params (which can be spoofed if someone specifies /?_orig=whatever), we could use a non-exported Symbol which would never match anything provided in the URL.

Best would be to completely separate this from storage, for example by setting it as a Symbol-accessed property of the returned search params cache interface (so it can be accessed from the outside).

suggestion: I would also choose a more explicit name, like parseInput. If _orig stands for origin (or original?), this has a different meaning for code that deals with URLs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for those suggestions, makes a lot of sense. _orig was meant to stand for original but yes great point on it conflicting w/ the real search params namespace.

Symbols are a bit new to me (but make a lot of sense for this!) - I think I've done what you suggested and it seems to work. I separated the cache out into a new type with the parsed search params + the input (using a non-exported symbol for the key). LMK if this is right or I misunderstood. (Also I used $input for the symbol name as I saw in some examples the $ prefix being used to designate symbols but if you prefer a plain variable name I can change it).

Thanks for the fast review!

@rikbrown rikbrown requested a review from franky47 May 12, 2024 15:51
return Object.freeze(c) as Readonly<ParsedSearchParams>

c[$input] = searchParams
Object.freeze(c)
Copy link
Member

Choose a reason for hiding this comment

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

Looks great! The only detail now is that Object.freeze only does so one level deep, so this should be called on c.searchParams (and also updated in the check above), as we want to freeze the storage values (freezing the Cache object itself doesn't do much as nobody has access to it from the outside).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks - updated!

@rikbrown rikbrown requested a review from franky47 May 12, 2024 21:36
Copy link
Member

@franky47 franky47 left a comment

Choose a reason for hiding this comment

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

LGTM, thank you!

@franky47 franky47 merged commit 2727585 into 47ng:next May 13, 2024
1 check failed
Copy link

🎉 This PR is included in version 1.17.3-beta.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Copy link

🎉 This PR is included in version 1.17.3 🎉

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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants