Data Fetching
When fetching data via the Recharge JavaScript SDK in Hydrogen we recommend always fetching data within a loader/action.
Make sure to set the 'Cache-Control': 'no-cache, no-store, must-revalidate', header in your loader/action responses to prevent any caching between users as well.
Update account nav
Update the AccountMenu component in account.jsx to add a subscriptions link.
- JavaScript
 - TypeScript
 
/app/routes/account.jsx
function AccountMenu() {
  function isActiveStyle({ isActive, isPending }) {
    return {
      fontWeight: isActive ? 'bold' : undefined,
      color: isPending ? 'grey' : 'black',
    };
  }
  return (
    <nav role="navigation">
      <NavLink to="/account/orders" style={isActiveStyle}>
        Orders  
      </NavLink>
       | 
      <NavLink to="/account/subscriptions" style={isActiveStyle}>
          Subscriptions  
      </NavLink>
       | 
      <NavLink to="/account/profile" style={isActiveStyle}>
          Profile  
      </NavLink>
       | 
      <NavLink to="/account/addresses" style={isActiveStyle}>
          Addresses  
      </NavLink>
       | 
      <Logout />
    </nav>
  );
}
/app/routes/account.tsx
function AccountMenu() {
  function isActiveStyle({ isActive, isPending }: { isActive: boolean; isPending: boolean }) {
    return {
      fontWeight: isActive ? 'bold' : undefined,
      color: isPending ? 'grey' : 'black',
    };
  }
  return (
    <nav role="navigation">
      <NavLink to="/account/orders" style={isActiveStyle}>
        Orders  
      </NavLink>
       | 
      <NavLink to="/account/subscriptions" style={isActiveStyle}>
          Subscriptions  
      </NavLink>
       | 
      <NavLink to="/account/profile" style={isActiveStyle}>
          Profile  
      </NavLink>
       | 
      <NavLink to="/account/addresses" style={isActiveStyle}>
          Addresses  
      </NavLink>
       | 
      <Logout />
    </nav>
  );
}
Fetch Subscriptions and Render Subscription List
Example subscriptions list page that renders a list of Subscriptions with links to a details page.
- JavaScript
 - TypeScript
 
/app/routes/account.subscriptions._index.jsx
import { Link, useLoaderData } from 'react-router';
import { Money } from '@shopify/hydrogen';
import { data } from '@shopify/remix-oxygen';
import { rechargeQueryWrapper } from '~/lib/rechargeUtils';
import { listSubscriptions } from '@rechargeapps/storefront-client';
/**
 * @type {MetaFunction}
 */
export const meta = () => {
  return [{ title: 'Subscriptions' }];
};
/**
 * @param {LoaderFunctionArgs}
 */
export async function loader({ context }) {
  const subscriptionsResponse = await rechargeQueryWrapper(
    session =>
      listSubscriptions(session, {
        limit: 25,
        status: 'active',
      }),
    context
  );
  return data(
    { subscriptionsResponse },
    {
      headers: {
        'Cache-Control': 'no-cache, no-store, must-revalidate',
      },
    }
  );
}
export default function Subscriptions() {
  /** @type {LoaderReturnData} */
  const {
    subscriptionsResponse: { subscriptions },
  } = useLoaderData();
  return (
    <div className="subscriptions">
      {subscriptions.length ? <SubscriptionsTable subscriptions={subscriptions} /> : <EmptySubscriptions />}
    </div>
  );
}
/**
 * @param {{subscriptions: import('@rechargeapps/storefront-client').Subscription[]}}
 */
function SubscriptionsTable({ subscriptions }) {
  return (
    <div className="acccount-subscriptions">
      {subscriptions.length ? (
        subscriptions.map(subscription => <SubscriptionItem key={subscription.id} subscription={subscription} />)
      ) : (
        <EmptySubscriptions />
      )}
    </div>
  );
}
function EmptySubscriptions() {
  return (
    <div>
      <p>You haven't placed any subscriptions yet.</p>
      <br />
      <p>
        <Link to="/collections">Start Shopping →</Link>
      </p>
    </div>
  );
}
/**
 * @param {{subscription: import('@rechargeapps/storefront-client').Subscription}}
 */
function SubscriptionItem({ subscription }) {
  return (
    <>
      <fieldset>
        <Link to={`/account/subscriptions/${btoa(subscription.id)}`}>
          <strong>#{subscription.id}</strong>
        </Link>
        <p>{`${subscription.product_title}${subscription.variant_title ? ` (${subscription.variant_title})` : ''}`}</p>
        <p>{new Date(subscription.created_at).toDateString()}</p>
        <p>{subscription.status}</p>
        <p>{`${subscription.quantity} Every ${subscription.order_interval_frequency} ${subscription.order_interval_unit}(s)`}</p>
        <Money
          data={{
            amount: subscription.price,
            currencyCode: subscription.presentment_currency ?? 'USD',
          }}
        />
        <Link to={`/account/subscriptions/${btoa(subscription.id)}`}>View Subscription →</Link>
      </fieldset>
      <br />
    </>
  );
}
/app/routes/account.subscriptions._index.tsx
import type { MetaFunction } from 'react-router';
import { Link, useLoaderData } from 'react-router';
import { Money } from '@shopify/hydrogen';
import type { LoaderFunctionArgs } from '@shopify/remix-oxygen';
import { data } from '@shopify/remix-oxygen';
import { rechargeQueryWrapper } from '~/lib/rechargeUtils';
import type { Subscription } from '@rechargeapps/storefront-client';
import { listSubscriptions } from '@rechargeapps/storefront-client';
import type { CurrencyCode } from '@shopify/hydrogen/storefront-api-types';
export const meta: MetaFunction = () => {
  return [{ title: 'Subscriptions' }];
};
/**
 * @param {LoaderFunctionArgs}
 */
export async function loader({ context }: LoaderFunctionArgs) {
  const subscriptionsResponse = await rechargeQueryWrapper(
    session =>
      listSubscriptions(session, {
        limit: 25,
        status: 'active',
      }),
    context
  );
  return data(
    { subscriptionsResponse },
    {
      headers: {
        'Cache-Control': 'no-cache, no-store, must-revalidate',
      },
    }
  );
}
export default function Subscriptions() {
  const {
    subscriptionsResponse: { subscriptions },
  } = useLoaderData<typeof loader>();
  return (
    <div className="subscriptions">
      {subscriptions.length ? <SubscriptionsTable subscriptions={subscriptions} /> : <EmptySubscriptions />}
    </div>
  );
}
/**
 * @param {{subscriptions: import('@rechargeapps/storefront-client').Subscription[]}}
 */
function SubscriptionsTable({ subscriptions }: { subscriptions: Subscription[] }) {
  return (
    <div className="acccount-subscriptions">
      {subscriptions.length ? (
        subscriptions.map(subscription => <SubscriptionItem key={subscription.id} subscription={subscription} />)
      ) : (
        <EmptySubscriptions />
      )}
    </div>
  );
}
function EmptySubscriptions() {
  return (
    <div>
      <p>You haven't placed any subscriptions yet.</p>
      <br />
      <p>
        <Link to="/collections">Start Shopping →</Link>
      </p>
    </div>
  );
}
/**
 * @param {{subscription: import('@rechargeapps/storefront-client').Subscription}}
 */
function SubscriptionItem({ subscription }: { subscription: Subscription }) {
  return (
    <>
      <fieldset>
        <Link to={`/account/subscriptions/${btoa(`${subscription.id}`)}`}>
          <strong>#{subscription.id}</strong>
        </Link>
        <p>{`${subscription.product_title}${subscription.variant_title ? ` (${subscription.variant_title})` : ''}`}</p>
        <p>{new Date(subscription.created_at).toDateString()}</p>
        <p>{subscription.status}</p>
        <p>{`${subscription.quantity} Every ${subscription.order_interval_frequency} ${subscription.order_interval_unit}(s)`}</p>
        <Money
          data={{
            amount: subscription.price,
            currencyCode: (subscription.presentment_currency ?? 'USD') as CurrencyCode,
          }}
        />
        <Link to={`/account/subscriptions/${btoa(`${subscription.id}`)}`}>View Subscription →</Link>
      </fieldset>
      <br />
    </>
  );
}
Subscription Details Route
Example subscriptions detail page that renders Subscription Details with a link to Active Churn Recovery landing page flow.
- JavaScript
 - TypeScript
 
/app/routes/account.subscriptions.$id.jsx
import { data, redirect } from '@shopify/remix-oxygen';
import { useLoaderData, NavLink } from 'react-router';
import { Money } from '@shopify/hydrogen';
import { rechargeQueryWrapper } from '~/lib/rechargeUtils';
import { getSubscription, getActiveChurnLandingPageURL } from '@rechargeapps/storefront-client';
/**
 * @type {MetaFunction<typeof loader>}
 */
export const meta = ({ data }) => {
  return [{ title: `Subscription ${data?.subscription?.id}` }];
};
/**
 * @param {LoaderFunctionArgs}
 */
export async function loader({ request, params, context }) {
  if (!params.id) {
    return redirect('/account/subscriptions');
  }
  const subscriptionId = atob(params.id);
  const subscription = await rechargeQueryWrapper(
    session =>
      getSubscription(session, subscriptionId, {
        include: ['address'],
      }),
    context
  );
  if (!subscription) {
    throw new Error('Subscription not found');
  }
  const cancelUrl = await rechargeQueryWrapper(
    session => getActiveChurnLandingPageURL(session, params.id, request.url),
    context
  );
  const { product } = await context.storefront.query(PRODUCT_QUERY, {
    variables: {
      id: `gid://shopify/Product/${subscription.external_variant_id.ecommerce}`,
    },
  });
  return data(
    {
      subscription,
      cancelUrl,
      product,
    },
    {
      headers: {
        'Cache-Control': 'no-cache, no-store, must-revalidate',
      },
    }
  );
}
export default function SubscriptionRoute() {
  /** @type {LoaderReturnData} */
  const { subscription, cancelUrl, product } = useLoaderData();
  const address = subscription.include?.address;
  return (
    <div className="account-subscription">
      <h2>Subscription No. {subscription.id}</h2>
      <p>Placed on {new Date(subscription.created_at).toDateString()}</p>
      <br />
      <div>
        <table>
          <thead>
            <tr>
              <th scope="col">Product</th>
              <th scope="col">Price</th>
              <th scope="col">Quantity</th>
              <th scope="col">Frequency</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>
                <div>
                  <NavLink to={`/products/${product?.handle}`}>View Product</NavLink>
                  <p>{subscription.product_title}</p>
                  {subscription.variant_title && <small>{subscription.variant_title}</small>}
                </div>
              </td>
              <td>
                <Money
                  data={{
                    amount: subscription.price,
                    currencyCode: subscription.presentment_currency ?? 'USD',
                  }}
                />
              </td>
              <td>{subscription.quantity}</td>
              <td>{`Every ${subscription.order_interval_frequency} ${subscription.order_interval_unit}(s)`}</td>
            </tr>
          </tbody>
        </table>
        <div>
          <h3>Shipping Address</h3>
          {address ? (
            <address>
              <p>
                {address.first_name && address.first_name + ' '}
                {address.last_name}
              </p>
              <p>{address.address1}</p>
              {address.address2 && <p>{address.address2}</p>}
              <p>
                {address.city} {address.province} {address.zip} {address.country_code}
              </p>
            </address>
          ) : (
            <p>No shipping address defined</p>
          )}
          <h3>Status</h3>
          <div>
            <p>{subscription.status}</p>
          </div>
        </div>
      </div>
      <br />
      {subscription.status === 'active' && <a href={cancelUrl}>Cancel Subscription</a>}
    </div>
  );
}
const PRODUCT_QUERY = `#graphql
  query getProductById($id: ID!) {
    product(id: $id) {
      id
      handle
    }
  }
`;
/app/routes/account.subscriptions.$id.tsx
import type { LoaderFunctionArgs } from '@shopify/remix-oxygen';
import { data, redirect } from '@shopify/remix-oxygen';
import type { MetaFunction } from 'react-router';
import { useLoaderData, NavLink } from 'react-router';
import { Money } from '@shopify/hydrogen';
import { rechargeQueryWrapper } from '~/lib/rechargeUtils';
import { getSubscription, getActiveChurnLandingPageURL } from '@rechargeapps/storefront-client';
import type { CurrencyCode } from '@shopify/hydrogen/storefront-api-types';
export const meta: MetaFunction<typeof loader> = ({ data }) => {
  return [{ title: `Subscription ${data?.subscription?.id}` }];
};
/**
 * @param {LoaderFunctionArgs}
 */
export async function loader({ request, params, context }: LoaderFunctionArgs) {
  if (!params.id) {
    return redirect('/account/subscriptions');
  }
  const subscriptionId = atob(params.id);
  const subscription = await rechargeQueryWrapper(
    session =>
      getSubscription(session, subscriptionId, {
        include: ['address'],
      }),
    context
  );
  if (!subscription) {
    throw new Error('Subscription not found');
  }
  const cancelUrl = await rechargeQueryWrapper(
    session => getActiveChurnLandingPageURL(session, subscriptionId, request.url),
    context
  );
  const { product } = await context.storefront.query(PRODUCT_QUERY, {
    variables: {
      id: `gid://shopify/Product/${subscription.external_variant_id.ecommerce}`,
    },
  });
  return data(
    {
      subscription,
      cancelUrl,
      product,
    },
    {
      headers: {
        'Cache-Control': 'no-cache, no-store, must-revalidate',
      },
    }
  );
}
export default function SubscriptionRoute() {
  const { subscription, cancelUrl, product } = useLoaderData<typeof loader>();
  const address = subscription.include?.address;
  return (
    <div className="account-subscription">
      <h2>Subscription No. {subscription.id}</h2>
      <p>Placed on {new Date(subscription.created_at).toDateString()}</p>
      <br />
      <div>
        <table>
          <thead>
            <tr>
              <th scope="col">Product</th>
              <th scope="col">Price</th>
              <th scope="col">Quantity</th>
              <th scope="col">Frequency</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>
                <div>
                  <NavLink to={`/products/${product?.handle}`}>View Product</NavLink>
                  <p>{subscription.product_title}</p>
                  {subscription.variant_title && <small>{subscription.variant_title}</small>}
                </div>
              </td>
              <td>
                <Money
                  data={{
                    amount: subscription.price,
                    currencyCode: (subscription.presentment_currency ?? 'USD') as CurrencyCode,
                  }}
                />
              </td>
              <td>{subscription.quantity}</td>
              <td>{`Every ${subscription.order_interval_frequency} ${subscription.order_interval_unit}(s)`}</td>
            </tr>
          </tbody>
        </table>
        <div>
          <h3>Shipping Address</h3>
          {address ? (
            <address>
              <p>
                {address.first_name && address.first_name + ' '}
                {address.last_name}
              </p>
              <p>{address.address1}</p>
              {address.address2 && <p>{address.address2}</p>}
              <p>
                {address.city} {address.province} {address.zip} {address.country_code}
              </p>
            </address>
          ) : (
            <p>No shipping address defined</p>
          )}
          <h3>Status</h3>
          <div>
            <p>{subscription.status}</p>
          </div>
        </div>
      </div>
      <br />
      {subscription.status === 'active' && <a href={cancelUrl}>Cancel Subscription</a>}
    </div>
  );
}
const PRODUCT_QUERY = `#graphql
  query getProductById($id: ID!) {
    product(id: $id) {
      id
      handle
    }
  }
`;