Skip to content

Conversation

@verma-divyanshu-git
Copy link
Contributor

@verma-divyanshu-git verma-divyanshu-git commented Nov 4, 2025

What

Adds Suspense support to LingoProviderWrapper so it shows a loading UI instead of a blank screen while the dictionary loads.

Why

Closes #1305

Right now LingoProviderWrapper returns null during dictionary load, which causes a jarring blank screen. This is especially noticeable on slower connections or when loading remote dictionaries.

How

  • Wrapped dictionary loading in a Suspense boundary with a resource pattern
  • Added LingoProviderFallback component as the default loading UI (accessible with proper ARIA attributes)
  • Added optional fallback prop so teams can provide custom loading UI
  • Updated tests to cover Suspense behavior, error boundaries, and custom fallbacks
  • Updated Vite demo to showcase the new API

Testing

  • Ran pnpm --filter @lingo.dev/_react test - all 74 tests pass
  • Tested in Vite demo with network throttling (Slow 3G)
  • Verified fallback renders during load and transitions smoothly to content
  • Confirmed no breaking changes (fully backwards compatible)
Screenshot 2025-11-05 at 1 18 17 AM

@sumitsaurabh927
Copy link
Contributor

Hey @verma-divyanshu-git please link the issue this will solve and only raise a PR after being assigned to an issue.

@sumitsaurabh927
Copy link
Contributor

please update base, it is out of date

@sumitsaurabh927
Copy link
Contributor

@verma-divyanshu-git you need to update the title:

Error: No release type found in pull request title " Add Suspense fallback to LingoProviderWrapper". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/

Available types:

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • refactor: A code change that neither fixes a bug nor adds a feature
  • perf: A code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
  • ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit

@sumitsaurabh927
Copy link
Contributor

And also make sure you're added changeset

@The-Best-Codes
Copy link
Contributor

I'd like to review this at some point, if that's okay. If I don't review it within 24 hours and you need to merge it, just merge it.

@verma-divyanshu-git verma-divyanshu-git changed the title Add Suspense fallback to LingoProviderWrapper feat(react): add Suspense fallback to LingoProviderWrapper Nov 5, 2025
@verma-divyanshu-git verma-divyanshu-git force-pushed the feat/1305-lingoprovider-suspense branch from 95a7b09 to 0500fad Compare November 5, 2025 05:49
@verma-divyanshu-git
Copy link
Contributor Author

verma-divyanshu-git commented Nov 5, 2025

hey, @sumitsaurabh927, @The-Best-Codes , I have made all the changes, sorry for the trouble

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces breaking changes. The documentation would also need to be updated (which only the team can do), the AdonisJS demo would likely break, and users may have to make changes to their code.
Because of this, I think feedback from the team is necessary (it might be better if the team made a change like this).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, thanks for the feedback! This doesn't break anything, existing code keeps working as is. The only change users see is a loading message instead of blank screen while the dictionary loads.

The fallback prop is optional. If you don't pass it, you get the default. If you pass null, you get the old behavior (no UI during load).

I added this because of the TODO comments in the code. If you'd rather handle it differently or want to discuss the approach first, just let me know and I can close this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah to me it looks like error handling will be different now because of the Suspense but I'll test it locally. Give me a sec.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested the Vite and Next.js demos locally and they seem to be working fine. I can't test the AdonisJS one right now... but I think it will be okay ✔️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks! AdonisJS uses the same client provider so should be fine, but let me know if you want me to verify anything specific.

@The-Best-Codes
Copy link
Contributor

The-Best-Codes commented Nov 6, 2025

One thing I notice: I can't remove the fallback. Setting it to null doesn't work, it continues to use the default one. IMO, the fallback should be null by default, and you can opt-in to using the default fallback or a custom component.

@verma-divyanshu-git
Copy link
Contributor Author

@The-Best-Codes , Sorry, my bad. The ?? operator treats null as a fallback trigger, so passing fallback={null} still shows the default.

Should I change it so the fallback defaults to null (no UI) and users opt-in with <LingoProviderFallback /> if they want the loading message?

@The-Best-Codes
Copy link
Contributor

@verma-divyanshu-git Yes, I think that would be best.

@verma-divyanshu-git verma-divyanshu-git force-pushed the feat/1305-lingoprovider-suspense branch from 491093e to 60d5ff7 Compare November 6, 2025 20:50
@verma-divyanshu-git
Copy link
Contributor Author

@The-Best-Codes , Done! Fallback now defaults to null (no UI). Users opt-in with fallback={<LingoProviderFallback />} if they want the loading message. Just pushed the fix.

Copy link
Contributor

@The-Best-Codes The-Best-Codes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, haven't tested locally yet but I think it will work fine!
Let's wait to see what the team thinks

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds React Suspense support to LingoProviderWrapper to display a loading UI instead of returning null during dictionary loading, improving user experience especially on slower connections.

Key changes:

  • Replaced useState/useEffect pattern with Suspense boundary and resource pattern for dictionary loading
  • Added LingoProviderFallback component with accessible ARIA attributes as default loading UI
  • Added optional fallback prop to allow custom loading UI

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
packages/react/src/client/provider.tsx Implements Suspense pattern with createDictionaryResource, adds DictionaryBoundary component, and exports LingoProviderFallback
packages/react/src/client/provider.spec.tsx Updates tests to verify Suspense behavior, custom fallback support, and error boundary propagation
demo/vite-project/src/main.tsx Demonstrates new API by importing and using LingoProviderFallback
.changeset/suspense-fallback.md Documents the feature as a minor version bump

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +149 to +155
const resource = useMemo(
() =>
createDictionaryResource({
load: () => props.loadDictionary(locale),
locale,
}),
[props.loadDictionary, locale],
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resource will be recreated whenever props.loadDictionary changes identity (e.g., on parent re-renders if an inline arrow function is passed). This causes the dictionary to reload unnecessarily. The dependency array should likely only include locale, or the function reference should be stabilized with useCallback in the calling code. Consider adding a comment documenting that loadDictionary should be stable, or use useRef to store the load function and only recreate the resource when locale changes.

Copilot uses AI. Check for mistakes.
@maxprilutskiy maxprilutskiy enabled auto-merge (squash) November 12, 2025 07:00
@maxprilutskiy maxprilutskiy self-requested a review November 12, 2025 07:00
@maxprilutskiy maxprilutskiy merged commit 4d2359a into lingodotdev:main Nov 12, 2025
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add React Suspense support to LingoProvider for better loading experience

4 participants