Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LDProvider using Suspense #227

Open
romulof opened this issue Nov 23, 2023 · 5 comments
Open

LDProvider using Suspense #227

romulof opened this issue Nov 23, 2023 · 5 comments
Labels
enhancement New feature or request

Comments

@romulof
Copy link

romulof commented Nov 23, 2023

Is your feature request related to a problem? Please describe.

Current initialisation is done by HOC and/or functions what generate the provider component for you. In some setups this can be troublesome to use, for example when you don't have much control over you app initialisation.

Describe the solution you'd like

Would be amazing if we could just use <LDProvider> and until it is ready, it will trigger React suspense mode. This is easily achieved by throwing a promise object. This object is caught by <Suspense> and it renders a fallback (aka loading component) until this promise resolves.

Describe alternatives you've considered

There are definitely other alternatives, but not as simple and supported by React itself.

Additional context

N/A

@yusinto
Copy link
Contributor

yusinto commented Dec 5, 2023

Thank you for your submission. We will discuss this internally and provide an update soon. Logged internally as 226042.

@yusinto yusinto added the enhancement New feature or request label Dec 5, 2023
@elGatoMantocko
Copy link

🤔 Just curious, can't you do something like this?

const LDProvider = lazy(async () => ({ default: await asyncWithLDProvider(config) }));
return (
  <Suspense>
    <LDProvider>{children}</LDProvider>
  </Suspense>
);

Anything I should be worried about with this workaround?

@romulof
Copy link
Author

romulof commented Jul 22, 2024

That's an interesting approach, @elGatoMantocko. I'll give it a try.

@RobinClowers
Copy link

The big problem with @elGatoMantocko's approach is that you can't change the context during the lifecycle of the app. I use a multi context, and the user can switch between organizations, which are part of the context, so I need a way to update the context as those change. I ended up creating a hook that handles initializing the LD client, and then passing that to LDProvider.

let ldClient: LDClientType | null = null;
let ldClientReadyResolve: () => void | null;
let ldClientReady: Promise<void> | null = new Promise((resolve) => {
  ldClientReadyResolve = resolve;
});

export function useLaunchDarklyClient(user: User) {
  if (ldClient) return ldClient;

  ldClient = LDClient.initialize(config.launchDarklyKey, buildLdContext(user), {
    bootstrap: "localStorage",
    sendEventsOnlyForVariation: true,
  });
  ldClient.on("ready", () => {
    ldClientReadyResolve();
    ldClientReady = null;
  });
  if (ldClientReady) {
    // Trigger suspense if the client isn't ready yet
    throw ldClientReady;
  }
  return ldClient;
}

@RobinClowers
Copy link

RobinClowers commented Oct 23, 2024

We just discovered that the approach I outlined above has a bug: the LDProvider is initialized without flag values, so if you check feature flags on initial render, you get incorrect values. Ideally the provider would pull the flags from the client in the constructor, but instead it waits for componentDidMount, which is after the initial render. At this point, I'm just going to write my own provider and useFlags hook, which is unfortunate. @yusinto any update on this feature request?

My implementation, in case anyone finds it useful.

export interface LaunchDarklyContextType {
  client: LDClient;
}

export const LaunchDarklyContext =
  React.createContext<LaunchDarklyContextType | null>(null);

export const LaunchDarklyProvider = ({
  children,
}: {
  children?: React.ReactNode;
}) => {
  const { data: user } = useCurrentUser();
  const ldClient = useLaunchDarklyClient(user);

  return (
    <LaunchDarklyContext.Provider
      value={{
        client: ldClient,
      }}
    >
      {children}
    </LaunchDarklyContext.Provider>
  );
};

export const useFlags = () => {
  const context = useContext(LaunchDarklyContext);
  if (!context) {
    throw new Error("useFlags must be used within LaunchDarklyProvider");
  }
  return context.client.allFlags();
};

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

No branches or pull requests

4 participants