# 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.