All files / integrations/src/remix index.js

47.61% Statements 10/21
43.75% Branches 7/16
62.5% Functions 5/8
50% Lines 10/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99                                                    1x                         1x 1x       1x 1x   1x                                                                                       3x   3x 3x 3x      
// src/remix/index.js
//
// Public entry point for @coherent.js/integrations/remix. Provides SSR
// integration and loader utilities for Remix projects.
//
// Remix (and therefore React) must be installed as a peer dependency to use
// this integration.
//
// Usage:
//   import { createRemixAdapter } from '@coherent.js/integrations/remix';
 
import { createElement } from 'react';
import { render } from '@coherent.js/core';
 
/**
 * Create a Remix adapter for Coherent.js
 *
 * @param {Object} [options] - Adapter options
 * @param {boolean} [options.hydrate] - Enable client-side hydration
 * @returns {Object} Remix adapter utilities
 *
 * @example
 * const adapter = createRemixAdapter();
 * const html = adapter.renderComponent(MyComponent, { title: 'Hello' });
 */
export function createRemixAdapter(_options = {}) {
  return {
    /**
     * Render a Coherent.js component to HTML
     */
    renderComponent(component, props = {}) {
      const def = typeof component === 'function' ? component(props) : component;
      return render(def);
    },
 
    /**
     * Create a Remix loader that renders a Coherent.js component
     */
    createLoader(component, getProps) {
      return async ({ request, params, context }) => {
        const props = getProps
          ? await getProps({ request, params, context })
          : { request, params };
 
        const def = typeof component === 'function' ? component(props) : component;
        const html = render(def);
 
        return new Response(html, {
          headers: { 'Content-Type': 'text/html; charset=utf-8' }
        });
      };
    },
 
    /**
     * Create a Remix action handler that processes form data
     * and renders a Coherent.js component with the result
     */
    createAction(component, handler) {
      return async ({ request, params, context }) => {
        const formData = await request.formData();
        const data = Object.fromEntries(formData);
        const result = await handler({ data, request, params, context });
 
        if (result instanceof Response) return result;
 
        const def = typeof component === 'function' ? component(result) : component;
        const html = render(def);
 
        return new Response(html, {
          headers: { 'Content-Type': 'text/html; charset=utf-8' }
        });
      };
    }
  };
}
 
/**
 * HOC: Wrap a Coherent.js component for use in Remix routes
 *
 * Returns a React component that renders the Coherent.js markup inside a
 * wrapper element via `dangerouslySetInnerHTML`. Returning the HTML string
 * itself would make React escape it and show the tags as text. The
 * Coherent.js renderer escapes text and attribute values, so only markup the
 * component describes (or passes through its explicit `html` field) is raw.
 *
 * @param {Function|Object} Component - Coherent.js component function or object
 * @param {Object} [options] - Wrapper options
 * @param {string} [options.as='div'] - Tag name of the wrapper element
 * @returns {Function} Remix-compatible React component
 */
export function withCoherent(Component, options = {}) {
  const { as = 'div' } = options;
 
  return function CoherentRemixComponent(props) {
    const def = typeof Component === 'function' ? Component(props) : Component;
    return createElement(as, { dangerouslySetInnerHTML: { __html: render(def) } });
  };
}