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

chore(website): preserve RulesTable filters state in searchParams #6568

Merged
merged 8 commits into from
Mar 24, 2023

Conversation

hasparus
Copy link
Contributor

@hasparus hasparus commented Mar 4, 2023

PR Checklist

Overview

Howdy! 👋

I added syncing rules table filters state to URL search params, and I wrote two test cases for it.

Solution

There are two tables, so I serialised the state to two search params, one for each table

  • supported-rules,
  • and extension-rules.

Each value is list of categories concatenated with dashes -.

If FilterMode is "include", we include the category as is

  • supported-rules=recommended-fixable

If FilterMode is "exclude", we prefix it with x. (We can't use ! in the URL.)

  • supported-rules=xrecommended-strict-xfixable

I grouped the state of filters into one objects so it's easier to stringify.

Non-solutions

I initially just added a useEffect to update search params after the state change, and useIsomorphicLayoutEffect, but the problem was that the initial state would be written to the URL before it was read. I ended up moving writing to the URL to the changeFilter function exported from useRuleFilters hook.

We need useIsomorphicLayoutEffect to read from the URL instead of just using the state initialiser, because initialising different state in SSG and post-hydration would give us a hydration mismatch.

Alternative solution

I noticed Docusaurus uses React Router, so in 7c2e81a I removed setState and synchronization and move the entire state to useLocation, what in theory sounded better, because we'd just keep it in one place. Unfortunately, this got me a bug where the state wasn't re-read post hydration (useLocation doesn't rerender again?) and I needed a layout effect again, so I ended up just reverting this change.

Example

Screen.Recording.2023-03-04.at.18.28.04.mov

@nx-cloud
Copy link

nx-cloud bot commented Mar 4, 2023

☁️ Nx Cloud Report

CI is running/has finished running commands for commit 0eb7299. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this branch


✅ Successfully ran 46 targets

Sent with 💌 from NxCloud.

@typescript-eslint
Copy link
Contributor

Thanks for the PR, @hasparus!

typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community.

The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately.

Thanks again!


🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint.

@netlify
Copy link

netlify bot commented Mar 4, 2023

Deploy Preview for typescript-eslint ready!

Name Link
🔨 Latest commit d37c066
🔍 Latest deploy log https://app.netlify.com/sites/typescript-eslint/deploys/641cd7e18980310008873bdf
😎 Deploy Preview https://deploy-preview-6568--typescript-eslint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@hasparus hasparus marked this pull request as draft March 4, 2023 13:51
@hasparus hasparus marked this pull request as ready for review March 4, 2023 19:21
@hasparus
Copy link
Contributor Author

hasparus commented Mar 4, 2023

It seems that the tests are failing, because they wait for the deploy preview and they timeout before it deploys.

image

@JoshuaKGoldberg
Copy link
Member

It seems that the tests are failing, because they wait for the deploy preview and they timeout before it deploys.

Yeah 😞. #6508


test('Accessibility', async ({ page }) => {
await new AxeBuilder({ page }).analyze();
});
Copy link
Member

Choose a reason for hiding this comment

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

😍 thanks for adding this in!

(I'll continue reviewing soon, just wanted to express appreciation sooner)

Copy link
Member

@JoshuaKGoldberg JoshuaKGoldberg left a comment

Choose a reason for hiding this comment

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

Yup, this looks great. Thanks for sending it in @hasparus!

Just one suggestion on refactoring RulesTable<>useRulesFilters a bit. What do you think?

I'm down to merge as-is if you don't like the suggestion, or make it myself if you don't have time.

@@ -122,78 +123,57 @@ export default function RulesTable({
}: {
extensionRules?: boolean;
}): JSX.Element {
const [filters, changeFilter] = useRulesFilters(
extensionRules ? 'extension-rules' : 'supported-rules',
);
Copy link
Member

Choose a reason for hiding this comment

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

[Cleanup] I'm not a big fan of hardcoding IDs on boolean logic. It tends to break over time, and makes it harder to understand where the IDs come from. How about giving the RulesTable component a mandatory id prop that's then passed directly to useRulesFilters?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree about hardcoding is no boolean logic, but I feel that

<RulesTable id="extension-rules">

might be a bit confusing given that the heading above receives id attribute from the markdown pipeline.

image

How about a prop ruleset: "supported-rules" | "extension-rules" that would subsume the boolean flag and be usable as a search param key?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I like that a lot!

Copy link
Contributor Author

@hasparus hasparus Mar 14, 2023

Choose a reason for hiding this comment

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

I added it in 642031f, but it seems there are indirect codecov changes that are failing the CI. Do you think it's connected to my change?

Copy link
Member

Choose a reason for hiding this comment

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

We can ignore codecov for these website PRs. We don't have great unit test coverage on it anyway. 🤷

@JoshuaKGoldberg JoshuaKGoldberg added the 1 approval One team member has approved this PR; a second should be enough to merge it label Mar 11, 2023
@bradzacher bradzacher added the package: website Issues related to the @typescript-eslint website label Mar 13, 2023
@bradzacher bradzacher changed the title feat(website): preserve RulesTable filters state in searchParams chore(website): preserve RulesTable filters state in searchParams Mar 13, 2023
@bradzacher bradzacher added the awaiting response Issues waiting for a reply from the OP or another party label Mar 13, 2023
@hasparus
Copy link
Contributor Author

hasparus commented Mar 17, 2023

@JoshuaKGoldberg The deploy has finished in 3 minutes, but the status check's been hanging for 19 hours. Is there a way we can stop it or restart it? This seems like a bug in the Netlify integration.

image

@Skn0tt sorry for pinging you here, but I couldn't find a contact button at https://app.netlify.com/sites/typescript-eslint/deploys/64131d041e22810009b38f2c.
Who should I bother when the Netlify GitHub integration acts out?

Copy link
Member

@Josh-Cena Josh-Cena left a comment

Choose a reason for hiding this comment

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

The implementation looks mostly good to me, but as some extra reading, you may want to try useSyncExternalStore (interesting read) ^^

const [state, setState] = useState(neutralFiltersState);

useIsomorphicLayoutEffect(() => {
const search = new URLSearchParams(window.location.search);
Copy link
Member

Choose a reason for hiding this comment

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

You can replace window.location with useLocation() so you get SSR safety for free

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I expected useLocation() to trigger re-renders whenever the location changes, as the docs would suggest: https://reactrouter.com/en/main/hooks/use-location

So using location from useLocation here wouldn't be the same logic, right?
And I'm in a useLayoutEffect which does not run on the server, so accessing window.location works.

Could you tell me more what do you mean by "SSR safety for free"?

Notice that I had rewritten this logic to react-router specific hooks, what I described in "Alternative solution" in the original post. It turned out to be buggy and harder to reason about than the less elegant, naive solution with setState and useLayoutEffect, so I reverted that change — 786557f.

The implementation looks mostly good to me, but as some extra reading, you may want to try useSyncExternalStore (interesting read) ^^

Nice article! Do you think I should rewrite this PR to use useHistorySelector presented in the post?

Copy link
Member

@Josh-Cena Josh-Cena Mar 18, 2023

Choose a reason for hiding this comment

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

I'm okay either way! I don't have strong opinions on this, and TBH my React is getting a bit rusty these days. Just thought you may be interested—since effects don't work well with SSR.

Could you tell me more what do you mean by "SSR safety for free"?

I mean you don't have to use useIsomorphicLayoutEffect and read from a client-only API inside. useLocation is available both server-side and client-side, so it's more robust.

Copy link
Contributor Author

@hasparus hasparus Mar 19, 2023

Choose a reason for hiding this comment

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

I mean you don't have to use useIsomorphicLayoutEffect and read from a client-only API inside. useLocation is available both server-side and client-side, so it's more robust.

Well, yes, that's true, but then we shouldn't use useState, because a change to the location will already re-render the component. Would you agree?

Would you prefer if I went back to the state from 7c2e81a? and write end-to-end tests to ensure it works?

@JoshuaKGoldberg
Copy link
Member

(I'll defer to @Josh-Cena - who is more knowledgeable than me on Docusaurus best practices anyway!)

@bradzacher bradzacher removed the awaiting response Issues waiting for a reply from the OP or another party label Mar 21, 2023
@hasparus hasparus requested review from Josh-Cena and removed request for JoshuaKGoldberg March 22, 2023 14:35
@JoshuaKGoldberg
Copy link
Member

Who should I bother when the Netlify GitHub integration acts out?

Oh sorry I missed this - it's been flaky for a while. We can ignore it. #6508 😞

armano2
armano2 previously approved these changes Mar 23, 2023
Copy link
Member

@armano2 armano2 left a comment

Choose a reason for hiding this comment

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

this looks great, if you fix those linting errors i think we are ready to merge

Copy link
Member

@JoshuaKGoldberg JoshuaKGoldberg left a comment

Choose a reason for hiding this comment

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

🚀 excellent, thanks @hasparus!

@JoshuaKGoldberg JoshuaKGoldberg merged commit d8e563b into typescript-eslint:main Mar 24, 2023
@hasparus
Copy link
Contributor Author

Thanks for the review and bearing with me, guys 🙏

@Skn0tt
Copy link

Skn0tt commented Apr 3, 2023

@Skn0tt sorry for pinging you here

Sorry for getting back so late, I was on PTO the past weeks. Not sure what happened here, but it looks like things are alright now? Let me know if not, and i'll see how I can help :)

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
1 approval One team member has approved this PR; a second should be enough to merge it package: website Issues related to the @typescript-eslint website
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Website: Sync Supported Rules filter state in the URL
6 participants