import * as React from "react"; import { Instance } from "mobx-state-tree"; import { DocumentNode } from "graphql"; import { Query, FetchPolicy } from "./Query"; import { MSTGQLStore } from "./MSTGQLStore"; // import react namespace only; statement gets removed after transpiling declare var ReactNamespace: typeof import("react"); export type QueryLike = | ((store: STORE) => Query) | Query | string | DocumentNode; export function createStoreContext>( React: typeof ReactNamespace ) { return React.createContext(null as any); } export async function getDataFromTree< STORE extends Instance >( tree: React.ReactElement, client: STORE, renderFunction: ( tree: React.ReactElement ) => string = require("react-dom/server").renderToStaticMarkup ): Promise { while (true) { const html = renderFunction(tree); if (client.__promises.size === 0) { return html; } await Promise.all(client.__promises.values()); } } function normalizeQuery, DATA>( store: STORE, query: QueryLike, { variables, fetchPolicy = "cache-and-network", }: { variables?: any; fetchPolicy?: FetchPolicy; } ): Query { if (typeof query === "function") return query(store); if (query instanceof Query) return query; return store.query(query, variables, { fetchPolicy: fetchPolicy, }); } export type UseQueryHookOptions = { store?: STORE; variables?: any; fetchPolicy?: FetchPolicy; }; export type UseQueryHookResult = { store: STORE; loading: boolean; error: any; data: { [key in keyof DATA]: DATA[key] } | undefined; // prevData: DATA | undefined // set of previously fetched values, in case the query was replaced query: Query | undefined; setQuery: (query: QueryLike) => void; }; export type UseQueryHook = ( query?: QueryLike, options?: UseQueryHookOptions ) => UseQueryHookResult; export function createUseQueryHook>( context: React.Context, React: typeof ReactNamespace ): UseQueryHook { return function ( queryIn: undefined | QueryLike = undefined, opts: UseQueryHookOptions = {} ) { const store = (opts && opts.store) || React.useContext(context); // const prevData = useRef() // TODO: is this useful? const [query, setQuery] = React.useState | undefined>(() => { if (!queryIn) return undefined; return normalizeQuery(store, queryIn, opts); }); const setQueryHelper = React.useCallback( (newQuery: QueryLike) => { // if the current query had results already, save it in prevData // if (query && query.data) prevData.current = query.data setQuery(normalizeQuery(store, newQuery, opts)); }, [] ); // if new query or variables are passed in, replace the query! React.useEffect(() => { if (!queryIn || typeof queryIn === "function") return; // ignore changes to initializer func setQueryHelper(queryIn); }, [queryIn, opts.fetchPolicy, JSON.stringify(opts.variables)]); // TODO: use a decent deep equal return { store, loading: query ? query.loading : false, error: query && query.error, data: query && query.data, // prevData: prevData.current, query, setQuery: setQueryHelper, }; }; }