Skip to content

Instantly share code, notes, and snippets.

@fdecampredon
Created December 28, 2022 13:26
Show Gist options
  • Save fdecampredon/e8e977ea935d8b74ab82afbd3a5b6cf3 to your computer and use it in GitHub Desktop.
Save fdecampredon/e8e977ea935d8b74ab82afbd3a5b6cf3 to your computer and use it in GitHub Desktop.

Revisions

  1. fdecampredon renamed this gist Dec 28, 2022. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. fdecampredon created this gist Dec 28, 2022.
    74 changes: 74 additions & 0 deletions preloadServerQuery.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,74 @@
    import { graphql } from 'graphql';
    import schema from './your_schema_path';
    import type { VariablesOf, GraphQLTaggedNode } from 'react-relay';
    import type {
    ConcreteRequest,
    PayloadData,
    OperationType,
    } from 'relay-runtime';

    export type ServerQuery<TQuery extends OperationType> = {
    id: string;
    variables: VariablesOf<TQuery>;
    payload: PayloadData;
    };

    const preloadServerQuery = async <TQuery extends OperationType>(
    gqlQuery: GraphQLTaggedNode,
    variables: VariablesOf<TQuery>,
    ): Promise<ServerQuery<TQuery>> => {
    if (typeof gqlQuery === 'function') {
    gqlQuery = gqlQuery();
    }
    if (gqlQuery.kind !== 'Request') {
    throw new Error(
    'preloadServerQuery: Expected a graphql`...` tagged query.',
    );
    }
    const request = gqlQuery as ConcreteRequest;
    const { params } = request;
    const queryVariables = { ...variables };
    const { providedVariables } = params as any;
    if (providedVariables) {
    Object.keys(providedVariables).forEach(key => {
    //@ts-expect-error no types
    queryVariables[key] = params.providedVariables[key].get();
    });
    }

    // fetch instead of graphql if necessary
    const response = await graphql({
    schema,
    // handle persisted queries is necessary
    source: params.text as string,
    variableValues: queryVariables,
    });

    return {
    id: params.id ?? params.cacheID!,
    variables,
    // see https://github.com/apollographql/apollo-server/issues/3149#issuecomment-1117566982
    payload: normalizeObject(response.data),
    };
    };

    export default preloadServerQuery;

    const normalizeObject = (obj: any): any => {
    if (typeof obj !== 'object' || obj == null) {
    return obj;
    }
    if (Array.isArray(obj)) {
    return obj.map(normalizeObject);
    }
    if (!(obj instanceof Object)) {
    const res = {} as any;
    const keys = Object.keys(obj);
    for (const key of keys) {
    res[key] = normalizeObject(obj[key]);
    }
    obj = res;
    }

    return obj;
    };
    41 changes: 41 additions & 0 deletions useServerQuery
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    import { useMemo } from 'react';
    import { useRelayEnvironment } from 'react-relay';
    // @ts-expect-error no types
    import useLazyLoadQueryNode from 'react-relay/lib/relay-hooks/useLazyLoadQueryNode';
    // @ts-expect-error no types
    import useMemoOperationDescriptor from 'react-relay/lib/relay-hooks/useMemoOperationDescriptor';
    import { __internal } from 'relay-runtime';
    import type { ServerQuery } from './preloadServerQuery';
    import type { OperationType, GraphQLTaggedNode } from 'relay-runtime';

    const { fetchQuery } = __internal;

    function useServerQuery<TQuery extends OperationType>(
    gqlQuery: GraphQLTaggedNode,
    serverQuery: ServerQuery<TQuery>,
    ): TQuery['response'] {
    const environment = useRelayEnvironment();
    const operation = useMemoOperationDescriptor(gqlQuery, serverQuery.variables);
    if (operation.request.node.params.id !== serverQuery.id) {
    throw Error(
    `useServerQuery(): Mismatched version for query '${operation.request.node.params.name}'`,
    );
    }

    // ugly hack to avoid commiting the payload multiple times
    useMemo(() => {
    environment.commitPayload(operation, serverQuery.payload);
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    return useLazyLoadQueryNode({
    componentDisplayName: 'useServerQuery()',
    fetchKey: null,
    fetchPolicy: 'store-only',
    fetchObservable: fetchQuery(environment, operation),
    query: operation,
    renderPolicy: null,
    });
    }

    export default useServerQuery;