hooks_use-hermes-template.js
import { React } from '@gravityforms/libraries';
import { createHermesClient, serialize, raw, args } from '@gravityforms/request';
const { useCallback, useMemo, useState } = React;
/**
* @module useHermesTemplate
* @description A hook to interact with the Hermes API using template literal syntax.
* This provides a more intuitive API that closely mirrors the server-side
* query syntax, making queries easier to write and debug.
*
* @since 5.1.0
*
* @param {object} props The props for the hook.
* @param {string} props.endpoint The Hermes AJAX endpoint.
* @param {string} props.queryAction The AJAX action for queries.
* @param {string} props.mutationAction The AJAX action for mutations.
* @param {string} props.security The security nonce.
* @param {string} props.queryKey The key in the request body for queries (default: 'query').
* @param {string} props.mutationKey The key in the request body for mutations (default: 'mutation').
*
* @return {object} The Hermes state and actions.
*
* @example
* const { query, mutation, isLoading } = useHermesTemplate();
*
* // Query with template literal
* const fetchContacts = async () => {
* const result = await query`{
* contact(${ args( { limit: 20 } ) }) {
* id,
* firstName,
* lastName
* }
* }`;
* return result?.data?.data?.contact;
* };
*
* // Mutation with template literal
* const createContact = async ( firstName, lastName ) => {
* await mutation`{
* insert_contact(objects: [${ { firstName, lastName } }]) {
* returning { id, firstName, lastName }
* }
* }`;
* };
*/
const useHermesTemplate = ( {
endpoint = '',
queryAction = '',
mutationAction = '',
security = '',
queryKey = 'query',
mutationKey = 'mutation',
} = {} ) => {
const [ isLoading, setIsLoading ] = useState( false );
/**
* @function handleLoadingChange
* @description Callback to update loading state when requests start/end.
*
* @since 5.1.0
*
* @param {boolean} loading Whether there are active requests.
*/
const handleLoadingChange = useCallback( ( loading ) => {
setIsLoading( loading );
}, [] );
const client = useMemo( () => {
if ( ! endpoint || ! queryAction || ! security ) {
return null;
}
return createHermesClient( {
endpoint,
queryAction,
mutationAction: mutationAction || queryAction,
security,
queryKey,
mutationKey,
onLoadingChange: handleLoadingChange,
} );
}, [ endpoint, queryAction, mutationAction, security, queryKey, mutationKey, handleLoadingChange ] );
/**
* @function query
* @description Tagged template function for Hermes queries.
* Use backticks to write queries in GraphQL-like syntax.
*
* @since 5.1.0
*
* @param {Array} strings The template literal strings.
* @param {...*} values The interpolated values.
*
* @return {Promise<object>} The query response.
*
* @example
* const result = await query`{
* contact(limit: ${ limit }, search: ${ searchTerm }) {
* id,
* firstName,
* lastName,
* email { address, isPrimary }
* }
* }`;
*/
const query = useCallback( ( strings, ...values ) => {
if ( ! client ) {
// eslint-disable-next-line no-console
console.warn( 'useHermesTemplate: Client not configured. Ensure endpoint, queryAction, and security are provided.' );
return Promise.resolve( { error: new Error( 'Hermes client not configured' ) } );
}
return client.query( strings, ...values );
}, [ client ] );
/**
* @function mutation
* @description Tagged template function for Hermes mutations.
* Use backticks to write mutations in GraphQL-like syntax.
*
* @since 5.1.0
*
* @param {Array} strings The template literal strings.
* @param {...*} values The interpolated values.
*
* @return {Promise<object>} The mutation response.
*
* @example
* // Insert
* await mutation`{
* insert_contact(objects: [${ { firstName: 'John', lastName: 'Doe' } }]) {
* returning { id, firstName, lastName }
* }
* }`;
*
* // Update
* await mutation`{
* update_contact(id: ${ contactId }, firstName: ${ newName }) {
* returning { id, firstName }
* }
* }`;
*
* // Delete
* await mutation`{
* delete_contact(id: ${ contactId }) {}
* }`;
*
* // Connect
* await mutation`{
* connect_contact_company(objects: [${ { from: contactId, to: companyId } }]) {}
* }`;
*/
const mutation = useCallback( ( strings, ...values ) => {
if ( ! client ) {
// eslint-disable-next-line no-console
console.warn( 'useHermesTemplate: Client not configured. Ensure endpoint, mutationAction, and security are provided.' );
return Promise.resolve( { error: new Error( 'Hermes client not configured' ) } );
}
return client.mutation( strings, ...values );
}, [ client ] );
return {
isLoading,
query,
mutation,
serialize,
raw,
args,
};
};
export default useHermesTemplate;