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

Newsletter: use with-site-settings.js #90605

Open
wants to merge 5 commits into
base: trunk
Choose a base branch
from

Conversation

enejb
Copy link
Member

@enejb enejb commented May 10, 2024

This PR is intended to fix #89109 issue.

It does so in a couple of ways.

  1. It creates a more simpler way of managing the unsaved state.
  2. It intrudes a more modern component with-site-settings HOC that is intended to replace the current wrap-settings-form this new component used the useQuery component instead of the current redux.

Proposed Changes

  • Fixes the current saving of newsletters.

Testing Instructions

  • Load up this PR.
  • Navigate to /settings/newsletter/example.com for an atomic and simple site.
  • Notice that you can change all the settings and it works as before.
  • Notice that stats works as before.

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
  • Have you checked for TypeScript, React or other console errors?
  • Have you used memoizing on expensive computations? More info in Memoizing with create-selector and Using memoizing selectors and Our Approach to Data
  • Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
  • For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?

@enejb enejb force-pushed the fix/newsletter-setting-saving-try-3 branch from 9349408 to 469e27d Compare May 10, 2024 22:39
@matticbot
Copy link
Contributor

matticbot commented May 10, 2024

Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

Sections (~809 bytes added 📈 [gzipped])

name                 parsed_size           gzip_size
settings-newsletter      +1648 B  (+0.3%)     +809 B  (+0.5%)

Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to.

Legend

What is parsed and gzip size?

Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
Gzip Size: Compressed size of the JS and CSS files. This much data needs to be downloaded over network.

Generated by performance advisor bot at iscalypsofastyet.com.

@enejb enejb requested review from a team May 11, 2024 13:37
@matticbot matticbot added [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels May 11, 2024
@enejb enejb self-assigned this May 11, 2024
@enejb enejb marked this pull request as ready for review May 11, 2024 13:39
@enejb enejb force-pushed the fix/newsletter-setting-saving-try-3 branch from 3ef99e9 to 63ba925 Compare May 17, 2024 21:57
Copy link
Member

@tyxla tyxla left a comment

Choose a reason for hiding this comment

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

Nice work overall, thanks @enejb!

However, do you have a migration plan from the Redux version of site settings for the rest of the settings pages?

RIght now, maintaining the site settings data in both React Query and Redux, and that can lead to subtle data sync issues. That can be fine if it's temporary, while migrating away the rest of the settings pages. However, if we're not planning to refactor the rest of the settings pages, we might be stuck with duplicated settings data forever, which I'd strongly recommend against.

import { useQuery } from '@tanstack/react-query';
import wp from 'calypso/lib/wp';

const useSiteSettingsQuery = ( siteId: number | null ) =>
Copy link
Member

Choose a reason for hiding this comment

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

In general, it's nice to see this refactored to React Query 👍

I'm a bit concerned about the duplication though, as all settings pages could take a long time to be refactored.

const queryClient = useQueryClient();
const mutation = useMutation( {
mutationFn: ( { settings } ) =>
wp.req.post( '/sites/' + siteId + '/settings', { apiVersion: '1.4' }, settings ),
Copy link
Member

Choose a reason for hiding this comment

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

There's some normalization of settings going on in the original Redux version:

dispatch( updateSiteSettings( siteId, normalizeSettings( body.updated ) ) );

Should this be carried here too?

queryClient.invalidateQueries( {
queryKey: [ 'site-settings', siteId ],
} );
queryOptions.onSuccess?.( ...args );
Copy link
Member

Choose a reason for hiding this comment

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

We might need to invalidate a few more things on success:

There's some normalization of settings going on in the original Redux version:

dispatch( requestSite( siteId ) );
dispatch( requestAdminMenu( siteId ) );

const useSiteSettingsQuery = ( siteId: number | null ) =>
useQuery( {
queryKey: [ 'site-settings', siteId ],
queryFn: () => wp.req.get( `/sites/${ siteId }/settings`, { apiVersion: '1.4' } ),
Copy link
Member

Choose a reason for hiding this comment

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

The existing wrapSettingsForm HoC has some complexity that we might not be considering here, like fetching Jetpack settings separately and combining them:

settings = { ...settings, ...jetpackSettings };

const withSiteSettings = createHigherOrderComponent( ( Wrapped ) => {
const WithSiteSettings = ( props ) => {
const { siteId } = props;
const [ unsavedSettings, setUnsavedSettings ] = useState( {} );
Copy link
Member

Choose a reason for hiding this comment

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

Nit: this creates a new object on every rerender. You might want to use lazy init:

Suggested change
const [ unsavedSettings, setUnsavedSettings ] = useState( {} );
const [ unsavedSettings, setUnsavedSettings ] = useState( () => {} );

[ dispatch ]
);

const trackTracksEvent = useCallback(
Copy link
Member

Choose a reason for hiding this comment

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

Not sure these wrappers add much value. I'd likely just use dispatch( recordTracksEvent(...) ).

context: mutateContext,
} = useUpdateSiteSettingsMutation( siteId, {
onMutate: async ( settings ) => {
const queryKey = [ 'site-settings', siteId ];
Copy link
Member

Choose a reason for hiding this comment

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

Query key could be abstracted to a reusable function to ensure we're using the same one.

},
onSuccess() {
dispatch( successNotice( translate( 'Settings saved successfully!' ), noticeOptions ) );
setUnsavedSettings( {} ); // clear dirty fields after successful save.
Copy link
Member

Choose a reason for hiding this comment

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

Should we always use the same empty object instead of generating a new one?

unsavedSettings
);
return (
<Wrapped
Copy link
Member

Choose a reason for hiding this comment

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

If we're doing this "the new way", it might be more convenient to just use a hook instead of creating a HoC.


const NewsletterSettings = () => {
const translate = useTranslate();

const siteId = useSelector( getSelectedSiteId ) ?? 0;
Copy link
Member

Choose a reason for hiding this comment

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

Why is it necessary to fall back to 0? Couldn't this bleed somewhere unexpected and cause issues?

Copy link
Member Author

Choose a reason for hiding this comment

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

It is not I was just running into issues with typescript when I used const siteId = useSelector( getSelectedSiteId )

@tyxla
Copy link
Member

tyxla commented May 21, 2024

This PR is intended to fix #89109 issue.

Can you also elaborate on what the fix is? Can we do the fix in a PR, and then do a migration to React Query in another one?

@enejb
Copy link
Member Author

enejb commented May 22, 2024

Thanks for the review @tyxla.

This PR is intended to fix #89109 issue.
Can you also elaborate on what the fix is? Can we do the fix in a PR, and then do a migration to React Query in another one?

I am not able to reproduce this issue any more and I am not sure how it was fixed. :(

When I was trying to fix this issue I notice a couple of things.

  1. We send the same data to two different endpoints. 🙃
  2. We send data that is not even changed to the Jetpack endpoint. Which was causing the issue.

I tried to fix this issue by addressing a creating a bunch of Prs. (#90531, #90294 and Automattic/jetpack#37190) But in the end I try to simply the react code using this PR that would make it easier to reason about and reduce the unintended consequences.

However, do you have a migration plan from the Redux version of site settings for the rest of the settings pages?

My plan is to create a drop in replacement for wrapSettingsForm and replace it where I see it one by one. Testing it across Jetpack, atomic and simple sites. I think this can be a lot of work it is used by 15 different components. https://github.com/search?q=repo%3AAutomattic%2Fwp-calypso%20wrapSettingsForm&type=code

I do agree that having state in two different places is not great. So I wanted to get your input on this :)

I think we could proceed in two different ways.

  1. Close this PR. Since the issue doesn't seem to exist. The long term plan for the newsletter settings page is to exist in the jetpack plugin or a stand alone plugin.
  2. Continue with this refactor since there are issue with the current code base that make things a bit more complex then it needs to be. (IMHO) but if we do I would need more help from calypso team in order to get all the code merged. Since this work is would not be my top priority since it currently doesn't have much impact on the user experience :)

I would love to hear your thoughts on this :)

I am happy to address the feedback after we come up with a plan

@tyxla
Copy link
Member

tyxla commented May 23, 2024

If you have time to refactor all components that use wrapSettingsForm that would be cool. I believe it can be a lot of work since there are many different site types to test, many different edge cases and many specificities that will make this task quite complex. If you feel like it's worth the time, go for it, but I'm not convinced we'll earn enough from refactoring to React Query to warrant such a refactor. That being said, if the bug is fixed, I'd likely suggest closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Newsletters [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unable to Update Newsletter Settings
3 participants