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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add context.storefront.mutate #193

Merged
merged 13 commits into from
Nov 17, 2022
Merged

Add context.storefront.mutate #193

merged 13 commits into from
Nov 17, 2022

Conversation

frandiox
Copy link
Contributor

@frandiox frandiox commented Nov 15, 2022

As mentioned in the team sync today, this is a suggestion to change the signature of our storefront.query function.

Before:

storefront.query({query: '', variables: {}, cache: CacheLong()});
storefront.query({mutation: '', variables: {}});

After

storefront.query('', {variables: {}, cache: CacheLong()});
storefront.mutate('', {variables: {}});

This seems more straightforward and needs simpler types. However, if we don't like this option I can revert the syntax and keep the other changes 馃憤


Aside from that, this changes the demo-store to start using context.storefront and remove the old getStorefrontData utility.
It also fixes LoaderArgs, ActionArgs, ActionFunction and LoaderFunction types.


It's easier to look at this PR commit by commit.

@frandiox frandiox requested review from blittle and a team November 15, 2022 08:52
Comment on lines 152 to 154
const mutationErrors = Object.values(data)
.map((value: any) => value?.customerUserErrors?.[0]?.message)
.filter(Boolean);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does this look correct? I'm not very familiar with customerUserErrors in mutations.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this looks right. @frehner what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the client should only handle connection errors e.g (errors.length) internally, and should return data unaltered to let the mutation callee handle the graphql errors on the action (so that we can display them on the UI)

Because cart and customer mutations return errors under different keys cartUserErrors vs customerUserErrors, I like to alias both of these as errors.

This way in an action we can do:

const {cart, errors} = await storefront.mutation(cartMutation)
const {customer, errors} = await storefront.mutation(customerMutation)

See:
cart mutation errors
customer mutation error

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah it's unfortunate that it appears that there are different error properties on different mutations. Ugh.

Copy link
Contributor Author

@frandiox frandiox Nov 16, 2022

Choose a reason for hiding this comment

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

Ugh I see. The problem is that storefront.query right now only returns data... We would need to change the signature from const data = to const {data, errors} =. This means we'd need to start adding .then(payload => payload.data) when using storefront.query in defer

Nevermind, for some reason I thought the customerUserErrors array was in errors instead of data. I'll update the code to handle the customerUserErrors on the user app side 馃憤

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think you need to change it because both customerUserErrors and cartUserErrors are nested within the data object

There's two types of errors:

  • fetch errors: You can deal with these in the client (as you are doing with errors.length
  • graphql errors: We should deal with these on the action/loader

What may have confused you is that I called customerUserErrors and cartUserErrors as errors. But what I meant is this:

const data = await storefront.mutation(cartMutation)
const {cart, cartUserErrors: errors} = data;

Does that make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I modified my comment earlier without refreshing the page so I didn't see your last comment @juanpprieto , but yeah it makes sense, thanks!

Comment on lines +12 to +22
export interface LoaderFunction {
(args: LoaderArgs): Promise<Response> | Response | Promise<AppData> | AppData;
}

export type ActionArgs = DataFunctionArgs & {
context: HydrogenContext;
};

export interface ActionFunction {
(args: ActionArgs): Promise<Response> | Response | Promise<AppData> | AppData;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Aren't these essentially the same as what Remix provides? Why not use their types instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think is because we are modifying DataFunctionArgs['context']?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes we are modifying context to be HydrogenContext, that's the only reason.

Copy link
Contributor

@juanpprieto juanpprieto left a comment

Choose a reason for hiding this comment

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

馃憤馃徏

Copy link
Contributor

@blittle blittle left a comment

Choose a reason for hiding this comment

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

A few nit picky items. Great work Fran!

Comment on lines 152 to 154
const mutationErrors = Object.values(data)
.map((value: any) => value?.customerUserErrors?.[0]?.message)
.filter(Boolean);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this looks right. @frehner what do you think?

packages/hydrogen/storefront.ts Outdated Show resolved Hide resolved
}

export async function getLayoutData(params: Params) {
export async function getLayoutData(
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit picky, but IMO we should get rid of this data index file, and just co-locate the query where it's used, in root.tsx

Copy link
Contributor Author

@frandiox frandiox Nov 16, 2022

Choose a reason for hiding this comment

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

Yeah I want to do that but perhaps in a different PR more focused on refactoring?
In fact, I know JP is modifying data/index to change things in Cart so I think it's better if we do this later to minimize conflicts with his PR.

@@ -60,7 +61,7 @@ export const action: ActionFunction = async ({request, context, params}) => {
},
});
} catch (error: any) {
if (error instanceof StorefrontApiError) {
if (isStorefrontApiError(error)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, exposing this as a utility function to easily identify storefront api errors 馃

@@ -14,8 +14,7 @@ export async function loader({params, context: {storefront}}: LoaderArgs) {
invariant(params.pageHandle, 'Missing page handle');

const {language} = getLocalizationFromLang(params.lang);
const {page} = await storefront.query<{page: Page}>({
query: PAGE_QUERY,
const {page} = await storefront.query<{page: Page}>(PAGE_QUERY, {
Copy link
Contributor

Choose a reason for hiding this comment

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

I like this change, query is required, so make it the first arg. Second arg is optional.

packages/hydrogen/storefront.ts Outdated Show resolved Hide resolved
Comment on lines 152 to 154
const mutationErrors = Object.values(data)
.map((value: any) => value?.customerUserErrors?.[0]?.message)
.filter(Boolean);
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah it's unfortunate that it appears that there are different error properties on different mutations. Ugh.

Copy link
Contributor

@lordofthecactus lordofthecactus left a comment

Choose a reason for hiding this comment

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

Good job @frandiox, I agree the separation feels better.

@@ -101,7 +101,7 @@ export const action: ActionFunction = async ({request, context, params}) => {
formDataHas(formData, 'newPassword') &&
(customer.password = formData.get('newPassword') as string);

await updateCustomer({customerAccessToken, customer});
await updateCustomer(context, {customerAccessToken, customer});
Copy link
Contributor

Choose a reason for hiding this comment

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

Having to pass context so that I can use the storefront querying / mutations feels verbose, specially in so many functions 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.

Yeah we are supposed to remove these functions and just have them inlined in loader and action, but that will come in another PR 馃憤

@frandiox frandiox merged commit ca61d77 into main Nov 17, 2022
@frandiox frandiox deleted the fd-storefront-mutate branch November 17, 2022 01:29
query: string,
payload?: StorefrontCommonOptions & {cache?: CachingStrategy},
) => {
query = minifyQuery(query);
Copy link
Contributor

Choose a reason for hiding this comment

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

Any value in providing an option for users to opt out of minifying for debugging, etc...

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

Successfully merging this pull request may close these issues.

None yet

6 participants