---
# System prepended metadata

title: Top level queries

---

# Top level queries

Peoblem of deep nested complex queries is somewhat a pain to write and impacts the code readability on client. This will serve as a guide on best practise and how to structure queries on the client.

Reference issues:

https://app.zenhub.com/workspaces/alkemio-development-5ecb98b262ebd9f4aec4194c/issues/gh/alkem-io/client-web/4055

https://app.zenhub.com/workspaces/alkemio-development-5ecb98b262ebd9f4aec4194c/issues/gh/alkem-io/server/2817

### The problem

Our queries follow our datamodel's inheritence schema which leads to deep nested queries. Often these queries need to be conditional depending on the context. (are we fetching data from a space/challenge/opportunity). This leads to hundreds of unnecessary lines of code just to call the right query and destruct the needed data from the correct result.

Example:
To get the Canvas value we need three different queries to be able to get the data from the correct context.
```graphql
query hubCanvasValues($hubId: UUID_NAMEID!, $calloutId: UUID_NAMEID!, $canvasId: UUID_NAMEID!) {
  hub(ID: $hubId) {
    id
    collaboration {
      id
      callouts(IDs: [$calloutId]) {
        id
        type
        authorization {
          id
          myPrivileges
        }
        canvases(IDs: [$canvasId]) {
          ...CanvasDetails
          ...CanvasValue
        }
      }
    }
  }
}

query challengeCanvasValues(
  $hubId: UUID_NAMEID!
  $challengeId: UUID_NAMEID!
  $calloutId: UUID_NAMEID!
  $canvasId: UUID_NAMEID!
) {
  hub(ID: $hubId) {
    id
    challenge(ID: $challengeId) {
      id
      collaboration {
        id
        callouts(IDs: [$calloutId]) {
          id
          type
          authorization {
            id
            myPrivileges
          }
          canvases(IDs: [$canvasId]) {
            ...CanvasDetails
            ...CanvasValue
          }
        }
      }
    }
  }
}

query opportunityCanvasValues(
  $hubId: UUID_NAMEID!
  $opportunityId: UUID_NAMEID!
  $calloutId: UUID_NAMEID!
  $canvasId: UUID_NAMEID!
) {
  hub(ID: $hubId) {
    id
    opportunity(ID: $opportunityId) {
      id
      collaboration {
        id
        callouts(IDs: [$calloutId]) {
          id
          type
          authorization {
            id
            myPrivileges
          }
          canvases(IDs: [$canvasId]) {
            ...CanvasDetails
            ...CanvasValue
          }
        }
      }
    }
  }
}

```

Inside the react components we use these queries to fetch the Canvas value.
```typescript=
  const skipHub = !Boolean(calloutId) || Boolean(challengeNameId) || Boolean(opportunityNameId) || !Boolean(canvasId);
  const skipChallenge =
    !Boolean(calloutId) || Boolean(opportunityNameId) || !Boolean(challengeNameId) || !Boolean(canvasId);
  const skipOpportunity = !Boolean(calloutId) || !Boolean(opportunityNameId) || !Boolean(canvasId);

  const {
    data: hubData,
    loading: loadingHubCanvasValue,
    subscribeToMore: subHub,
  } = useHubCanvasValuesQuery({
    errorPolicy: 'all',
    fetchPolicy: 'network-only',
    nextFetchPolicy: 'cache-and-network',
    skip: skipHub,
    variables: {
      hubId: hubNameId!,
      canvasId: canvasId!,
      calloutId: calloutId!,
    },
  });

  const {
    data: challengeData,
    loading: loadingChallengeCanvasValue,
    subscribeToMore: subChallenge,
  } = useChallengeCanvasValuesQuery({
    errorPolicy: 'all',
    fetchPolicy: 'network-only',
    nextFetchPolicy: 'cache-and-network',
    skip: skipChallenge,
    variables: {
      hubId: hubNameId!,
      challengeId: challengeNameId!,
      canvasId: canvasId!,
      calloutId: calloutId!,
    },
  });

  const {
    data: opportunityData,
    loading: loadingOpportunityCanvasValue,
    subscribeToMore: subOpportunity,
  } = useOpportunityCanvasValuesQuery({
    errorPolicy: 'all',
    fetchPolicy: 'network-only',
    nextFetchPolicy: 'cache-and-network',
    skip: skipOpportunity,
    variables: {
      hubId: hubNameId!,
      opportunityId: opportunityNameId!,
      calloutId: calloutId!,
      canvasId: canvasId!,
    },
  });
```

And then to get the data itself we do:
```typescript
    const canvas = useMemo(() => {
    const sourceArray =
      getCanvasCalloutContainingCanvas(hubData?.hub.collaboration?.callouts, canvasId!)?.canvases ||
      getCanvasCalloutContainingCanvas(challengeData?.hub.challenge.collaboration?.callouts, canvasId!)?.canvases ||
      getCanvasCalloutContainingCanvas(opportunityData?.hub.opportunity.collaboration?.callouts, canvasId!)?.canvases;

    return sourceArray?.find(c => c.id === canvasId) as Canvas | undefined;
  }, [hubData, challengeData, opportunityData, canvasId]);
```

All this seems like a lot of boilerplate to fetch data from a single entity. Is there a better way to do it?


### The solution

In most cases, a top level query to access a nested entity help us have much cleaner and readable codebase.

Adding the ability to query a Canvas entity would remove 90% of boilerplate from the example above.

The only query we need is:
```graphql
query canvasWithValue($canvasId: UUID!) {
  canvas(ID: $canvasId) {
    ...CanvasDetails
    ...CanvasValue
  }
}
```

And to fetch the data from within the component we need:
```typescript
  const skipCanvasQuery = !Boolean(canvasId);
  const {
    data: canvasWithValueData,
    loading: loadingCanvasWithValue,
    subscribeToMore: subscribeToCanvas,
  } = useCanvasWithValueQuery({
    errorPolicy: 'all',
    fetchPolicy: 'network-only',
    nextFetchPolicy: 'cache-and-network',
    skip: skipCanvasQuery,
    variables: {
      canvasId: canvasId!,
    },
  });
```

To extract the data we now need only:
```typescript
  const canvas = canvasWithValueData?.canvas;
```

When using the component we remove the unnecessary props because we already know the context and we know the exact entity which details we want to fetch.

```jsx
    <CanvasValueContainer
        canvasId={canvas?.id}
        calloutId={calloutId}
        hubNameId={urlParams.hubNameId ?? ''}
        challengeNameId={urlParams.challengeNameId}
        opportunityNameId={urlParams.opportunityNameId}
    >
```
Becomes
```jsx
    <CanvasValueContainer canvasId={canvas?.id} >
```

In total we end up with a lot less lines of code and a much concise component logic.


### Conclusion

There are cases in which it is not possible to perform direct queries on deeply nested entities simply because we don't have the `id` of the nested object. In these scenarios it is advised to divide queries into two types:
*    general info queries
*    detailed info queries

General info queries shoud be performed on a higher level to gather the general info like `id`s etc. and top level query should be called from view components to get detailed information for a specific entity.




List of entities exposed as a top level query:
*   Collaboration
*   Context
*   Community
*   Canvas (Whiteboard)
    


List of entities that are a good fit for top level queries:
* Profile
* Callout
* Card
* WhiteboardTemplate
* CardTemplate
* InnovationFlowTemplate


Exposing top level queries is fairly simple and in most cases improves the client codebase greatly so developers should be encouraged to use the context/detailed info pattern.



