Skip to content

feat: support eager HyperElements init and graceful error handling - #17

Open
aritro2002 wants to merge 2 commits into
mainfrom
feat/eager-init-and-error-resilience
Open

feat: support eager HyperElements init and graceful error handling#17
aritro2002 wants to merge 2 commits into
mainfrom
feat/eager-init-and-error-resilience

Conversation

@aritro2002

Copy link
Copy Markdown
Contributor

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

Problem
Previously, merchants had to wait for the clientSecret to be available before mounting . This forced a pattern where HyperElements was placed deep inside the component tree — often co-located with the payment sheet itself — just to delay initialisation until all required data was ready:

// Old forced pattern — HyperElements buried inside checkout component
{clientSecret && (
  <HyperElements hyper={hyperPromise} options={{ clientSecret }}>
    <PaymentSheet />
  </HyperElements>
)}

This limited layout flexibility and prevented merchants from initialising the SDK at a higher level in the tree (e.g. at a route or page level).
What Changed

  1. options now accepts both a plain object and a Promise
    A new Utils.normalizeToPromise helper normalises the options prop at runtime — if a plain JSON object is passed it wraps it in Promise.resolve, if a Promise is passed it is used as-is. This means merchants can now pass a promise that resolves when their async data (like clientSecret) is ready:
// New pattern — initialise at the top level, pass a promise for options
const optionsPromise = fetchClientSecret().then(secret => ({ clientSecret: secret }));
<HyperElements hyper={hyperPromise} options={optionsPromise}>
  <App />
</HyperElements>

The plain JSON form continues to work exactly as before for full backward compatibility:

// Still works — no migration required
<HyperElements hyper={hyperPromise} options={{ clientSecret }}>
  <PaymentSheet />
</HyperElements>

Why Promise for options?

When merchants pass options as an inline object literal, a new object reference is created on every parent re-render even if the values are identical, causing the SDK to re-initialise more than once. Passing options as a Promise gives a stable reference — the Promise is created once and its identity never changes across re-renders, so the useEffect dependency check skips re-running the initialisation. This guarantees the SDK initialises exactly once when the promise resolves, regardless of how many times the parent re-renders due to unrelated state changes. Plain JSON options continue to work correctly as long as the reference is kept stable via useState or useMemo, but the Promise form is the recommended pattern when options depend on async data or when the parent re-renders frequently.

  1. options added to useEffect dependency array
    The effect that initialises the SDK now re-runs when options changes. This means if a merchant passes a plain object that updates (e.g. clientSecret goes from "" to a real value), the SDK will re-initialise with the correct options without requiring the merchant to conditionally mount/unmount HyperElements.

  2. Graceful error handling — merchant's UI never breaks
    Previously, a misconfiguration or network failure during SDK initialisation would result in an unhandled promise rejection that could silently break child components. Now each component catches initialisation errors and logs a warning, while leaving children to render normally:
    [HyperElements] Failed to initialise hyper promise:
    The merchant's page continues to render — buttons, layout, and non-payment UI are unaffected. Only the payment-specific hooks (useHyper, useElements) will return their default no-op values, which is safe and explicit.

  3. Migrated to ReScript 11 native Promise API
    Removed usage of the deprecated Js.Promise.then_ / Js.Promise.catch bindings across all three components. All async chains now use the native Promise.then / Promise.catch from @rescript/core.

Files Changed

src/Utils.res New — normalizeToPromise helper
src/components/HyperElements.res Eager init, Promise options support, error handling, dep array
src/components/Elements.res Same as above
src/components/HyperManagementElements.res Same as above
dist/bundle.js + *.bs.js Compiled output
Backward Compatibility

  • Plain JSON options prop: fully supported, no changes required
  • Existing HyperElements usage with conditional mounting: still works, merchants can migrate at their own pace
  • No changes to any public hook APIs (useHyper, useElements, usePaymentSession)

How did you test it?

Screen.Recording.2026-07-23.at.2.59.19.pm.mov

Version Update

  • I have bumped the package version in package.json following semantic versioning:
    • x.x.x for major changes (breaking).
    • x.x.x for minor changes (new feature, no breaking changes).
    • x.x.x for patch changes (bug fixes, minor improvements).

Checklist

  • I ran npm run re:build and verified the build artifacts.
  • I reviewed the code for style, readability, and consistency.
  • I verified the changes are backward compatible (if applicable).
  • I tested this change in a real or simulated consuming project.
  • I updated documentation, README, or usage examples if necessary.

@aritro2002 aritro2002 self-assigned this Jul 23, 2026
@aritro2002 aritro2002 added the enhancement New feature or request label Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant