feat: support eager HyperElements init and graceful error handling - #17
Open
aritro2002 wants to merge 2 commits into
Open
feat: support eager HyperElements init and graceful error handling#17aritro2002 wants to merge 2 commits into
aritro2002 wants to merge 2 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Type of Change
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:
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
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:
The plain JSON form continues to work exactly as before for full backward compatibility:
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.
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.
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.
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
How did you test it?
Screen.Recording.2026-07-23.at.2.59.19.pm.mov
Version Update
package.jsonfollowing semantic versioning:x.x.xfor major changes (breaking).x.x.xfor minor changes (new feature, no breaking changes).x.x.xfor patch changes (bug fixes, minor improvements).Checklist
npm run re:buildand verified the build artifacts.