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