HoudiniGraphql / houdini

The disappearing GraphQL framework
http://www.houdinigraphql.com
MIT License
917 stars 99 forks source link

Querying different fields on the same object in +layout and +page causes values to disappear in browser #1104

Closed douglasward closed 11 months ago

douglasward commented 1 year ago

Describe the bug

It sounds similar to this issue https://github.com/HoudiniGraphql/houdini/issues/1020 but I am not using fragments, and even if i disable masking it doesn't seem to help.

If you run a query on an object in both +layout and +page then the values will be removed from the browser for the +page query.

Severity

serious, but I can work around it

Steps to Reproduce the Bug

query in +layout

query LayoutQuery {
  tenant {
    id
    featureFlags
  }
}

query in +page

query PageQuery {
  tenant {
    id
    name
  }
}

Both queries are run, but nothing is displayed in the in the page component:

<script lang="ts">
  export let data;
  $: ({ PageQuery } = data);
</script>

{$PageQuery?.data?.contact?.id}

Reproduction

No response

jycouet commented 1 year ago

Is it a typo the {$PageQuery?.data?.contact?.id}? Shouldn't it be something with tenant? Could you check {$PageQuery?.data}?

douglasward commented 1 year ago

ahh yes, I'm very sorry - I didn't catch that copy and paste mistake in my final minified test before leaving the office. So correcting that to tenant fixes showing the id in the template, but my actually use case was slightly different and involves custom load functions.

I am waiting for the data to be loading in the load function so I can initialize superforms with the data. My +page.ts looks like this:

import { load_PageQuery } from "$houdini";
import { formSchema } from "$lib/form-schemas/formSchema.schema";
import { waitForData } from "$lib/utils/graphql.util";
import { superValidate } from "sveltekit-superforms/server";

export const load = async (event) => {
  const { PageQuery } = await load_PageQuery({ event });

  const { tenant } = (await waitForData(PageQuery)) ?? {};

  const form = await superValidate(tenant, formSchema);

  return { form };
};

We had written a waitForData helper that was waiting for the query to resolve, like this:

import type { GraphQLObject, GraphQLVariables, QueryStore } from "$houdini";

export const waitForData = async <Data extends GraphQLObject, Input extends GraphQLVariables>(
  store: QueryStore<Data, Input>
) => {
  return await new Promise<Data | null>((resolve) => {
    store.subscribe(({ data }) => resolve(data));
  });
};

This had been working fine, but when adding the +layout queries of the same object, subscribing to the +page query would first return source cache and tenant null and then return source network and tenant with correct data. Since we were resolving the first time it returned something, we resolved with null. I have now updated the function to look like this:

export const waitForData = async <Data extends GraphQLObject, Input extends GraphQLVariables>(
  store: QueryStore<Data, Input>
) => {
  return await new Promise<Data | null>((resolve) => {
    store.subscribe(({ data, source, fetching }) => {
      console.log(data, source);
      if (
        !fetching &&
        ((source === "cache" && data && Object.keys(data).every((key) => data[key])) ||
          source === "network")
      ) {
        resolve(data);
      }
    });
  });
};

Which has "fixed" my use-case, but feels quite hacky. Is there a better way to wait for data in load functions to ease working with superforms?

jycouet commented 1 year ago

You could check out the blocking param.

const { PageQuery } = await load_PageQuery({ event, blocking: true });
douglasward commented 1 year ago

That doesn't seem to have any effect.

douglasward commented 1 year ago

ahh but I have defaultPartial enabled, maybe I should try disabling that to see if it has any effect

jycouet commented 1 year ago

That doesn't seem to have any effect.

A "real await" should happen with blocking: true, and you should have something in result

const result = await load_PageQuery({ event, blocking: true });
douglasward commented 1 year ago

Yes, that's what I had. I just tried with disabling deafultPartial and that did the trick.

Here with partial + blocking set to true:

{contact: {…}} 'network' 'CustomerLayout'
{contact: null} 'cache' 'CustomerPrivacy'
{contact: {…}} 'cache' 'CustomerPrivacy'
{contact: {…}} 'network' 'CustomerPrivacy'

And here with partial set to false and blocking set to true:

{contact: {…}} 'network' 'CustomerLayout'
{contact: {…}} 'network' 'CustomerPrivacy'
douglasward commented 1 year ago

So I guess the issue is "solved" here, but maybe it is worth updating the docs regarding the usage of defaultPartial and blocking together so that confusion can be avoided in the future?