Skip to content

Commit db68687

Browse files
committed
fix: docs and issues
- fix cloning of context object due to vue proxy - updated readme with new sections - renamed useClientEvent to useOnEvent
1 parent fc0aa04 commit db68687

File tree

16 files changed

+598
-191
lines changed

16 files changed

+598
-191
lines changed

packages/browser-sdk/README.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,9 @@ const { isEnabled } = reflagClient.getFlag("huddle");
289289

290290
This eliminates loading states and improves performance by avoiding the initial flags API call.
291291

292-
## Updating user/company/other context
292+
## Context management
293+
294+
### Updating user/company/other context
293295

294296
Attributes given for the user/company/other context in the ReflagClient constructor can be updated for use in flag targeting evaluation with the `updateUser()`, `updateCompany()` and `updateOtherContext()` methods.
295297
They return a promise which resolves once the flags have been re-evaluated follow the update of the attributes.
@@ -306,6 +308,57 @@ await reflagClient.updateUser({ voiceHuddleOptIn: (!isEnabled).toString() });
306308

307309
> [!NOTE] > `user`/`company` attributes are also stored remotely on the Reflag servers and will automatically be used to evaluate flag targeting if the page is refreshed.
308310
311+
### setContext()
312+
313+
The `setContext()` method allows you to replace the entire context (user, company, and other attributes) at once. This method is useful when you need to completely change the context, such as when a user logs in or switches between different accounts.
314+
315+
```ts
316+
await reflagClient.setContext({
317+
user: {
318+
id: "new-user-123",
319+
name: "Jane Doe",
320+
email: "jane@example.com",
321+
role: "admin",
322+
},
323+
company: {
324+
id: "company-456",
325+
name: "New Company Inc",
326+
plan: "enterprise",
327+
},
328+
other: {
329+
feature: "beta",
330+
locale: "en-US",
331+
},
332+
});
333+
```
334+
335+
The method will:
336+
337+
- Replace the entire context with the new values
338+
- Re-evaluate all flags based on the new context
339+
- Update the user and company information on Reflag servers
340+
- Return a promise that resolves once the flags have been re-evaluated
341+
342+
### getContext()
343+
344+
The `getContext()` method returns the current context being used for flag evaluation. This is useful for debugging or when you need to inspect the current user, company, and other attributes.
345+
346+
```ts
347+
const currentContext = reflagClient.getContext();
348+
console.log(currentContext);
349+
// {
350+
// user: { id: "user-123", name: "John Doe", email: "john@example.com" },
351+
// company: { id: "company-456", name: "Acme Inc", plan: "enterprise" },
352+
// other: { locale: "en-US", feature: "beta" }
353+
// }
354+
```
355+
356+
The returned context object contains:
357+
358+
- `user`: Current user attributes (if any)
359+
- `company`: Current company attributes (if any)
360+
- `other`: Additional context attributes not related to user or company
361+
309362
## Toolbar
310363

311364
The Reflag Toolbar is great for toggling flags on/off for yourself to ensure that everything works both when a flag is on and when it's off.

packages/browser-sdk/src/client.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ import {
1616
RawFlags,
1717
} from "./flag/flags";
1818
import { ToolbarPosition } from "./ui/types";
19-
import { API_BASE_URL, APP_BASE_URL, SSE_REALTIME_BASE_URL } from "./config";
19+
import {
20+
API_BASE_URL,
21+
APP_BASE_URL,
22+
IS_SERVER,
23+
SSE_REALTIME_BASE_URL,
24+
} from "./config";
2025
import { ReflagContext, ReflagDeprecatedContext } from "./context";
2126
import { HookArgs, HooksManager, State } from "./hooksManager";
2227
import { HttpClient } from "./httpClient";
@@ -393,9 +398,11 @@ export class ReflagClient {
393398
this.publishableKey = opts.publishableKey;
394399
this.logger =
395400
opts?.logger ?? loggerWithPrefix(quietConsoleLogger, "[Reflag]");
401+
402+
// Create the context object making sure to clone the user and company objects
396403
this.context = {
397-
user: opts?.user?.id ? opts.user : undefined,
398-
company: opts?.company?.id ? opts.company : undefined,
404+
user: opts?.user?.id ? { ...opts.user } : undefined,
405+
company: opts?.company?.id ? { ...opts.company } : undefined,
399406
other: { ...opts?.otherContext, ...opts?.other },
400407
};
401408

@@ -487,7 +494,7 @@ export class ReflagClient {
487494
this.setState("initializing");
488495

489496
const start = Date.now();
490-
if (this.autoFeedback) {
497+
if (this.autoFeedback && !IS_SERVER) {
491498
// do not block on automated feedback surveys initialization
492499
this.autoFeedbackInit = this.autoFeedback.initialize().catch((e) => {
493500
this.logger.error("error initializing automated feedback surveys", e);
@@ -656,22 +663,33 @@ export class ReflagClient {
656663

657664
/**
658665
* Update the context.
659-
* Performs a shallow merge with the existing context.
660-
* It will not update the context if nothing has changed.
666+
* Replaces the existing context with a new context.
661667
*
662668
* @param context The context to update.
663669
*/
664-
async updateContext({ otherContext, ...context }: ReflagDeprecatedContext) {
670+
async setContext({ otherContext, ...context }: ReflagDeprecatedContext) {
665671
const userIdChanged =
666672
context.user?.id && context.user.id !== this.context.user?.id;
673+
674+
// Create a new context object making sure to clone the user and company objects
667675
const newContext = {
668-
...this.context,
669-
...context,
670-
other: { ...this.context.other, ...otherContext, ...context.other },
676+
user: context.user?.id ? { ...context.user } : undefined,
677+
company: context.company?.id ? { ...context.company } : undefined,
678+
other: { ...otherContext, ...context.other },
671679
};
672680

681+
if (!context.user?.id) {
682+
this.logger.warn("No user Id provided in context, user will be ignored");
683+
}
684+
if (!context.company?.id) {
685+
this.logger.warn(
686+
"No company Id provided in context, company will be ignored",
687+
);
688+
}
689+
673690
// Nothing has changed, skipping update
674691
if (deepEqual(this.context, newContext)) return;
692+
console.log("diffed");
675693
this.context = newContext;
676694

677695
if (context.company) {

packages/browser-sdk/src/flag/flags.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,8 @@ export class FlagsClient {
271271
}
272272

273273
setFetchedFlags(fetchedFlags: RawFlags, triggerEvent = true) {
274-
this.fetchedFlags = fetchedFlags;
274+
// Create a new fetched flags object making sure to clone the flags
275+
this.fetchedFlags = { ...fetchedFlags };
275276
this.warnMissingFlagContextFields(fetchedFlags);
276277
this.updateFlags(triggerEvent);
277278
}

packages/react-sdk/README.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -744,11 +744,7 @@ import { useClient } from "@reflag/react-sdk";
744744
function LoggingWrapper({ children }: { children: ReactNode }) {
745745
const client = useClient();
746746

747-
useEffect(() => {
748-
client.on("check", (evt) => {
749-
console.log(`The flag ${evt.key} is ${evt.value} for user.`);
750-
});
751-
}, [client]);
747+
console.log(client.getContext());
752748

753749
return children;
754750
}
@@ -757,6 +753,7 @@ function LoggingWrapper({ children }: { children: ReactNode }) {
757753
### `useIsLoading()`
758754
759755
Returns the loading state of the flags in the `ReflagClient`.
756+
Initially, the value will be `true` if no bootstrap flags have been provided and the client has not be initialized.
760757
761758
```tsx
762759
import { useIsLoading } from "@reflag/react-sdk";
@@ -773,6 +770,22 @@ function LoadingWrapper({ children }: { children: ReactNode }) {
773770
}
774771
```
775772
773+
### `useOnEvent()`
774+
775+
Attach a callback handler to client events to act on changes. It automatically disposes itself on unmount.
776+
777+
```tsx
778+
import { useOnEvent } from "@reflag/react-sdk";
779+
780+
function LoggingWrapper({ children }: { children: ReactNode }) {
781+
useOnEvent("flagsUpdated", (newFlags) => {
782+
console.log(newFlags);
783+
});
784+
785+
return children;
786+
}
787+
```
788+
776789
## Migrating from Bucket SDK
777790
778791
If you have been using the Bucket SDKs, the following list will help you migrate to Reflag SDK:

packages/react-sdk/dev/plain/app.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from "react";
1+
import React, { useState } from "react";
22

33
import {
44
FlagKey,
@@ -12,6 +12,7 @@ import {
1212
useClient,
1313
ReflagBootstrappedProvider,
1414
RawFlags,
15+
useOnEvent,
1516
} from "../../src";
1617

1718
// Extending the Flags interface to define the available features
@@ -231,13 +232,9 @@ function CustomToolbar() {
231232
const client = useClient();
232233
const [flags, setFlags] = useState<RawFlags>(client.getFlags() ?? {});
233234

234-
useEffect(() => {
235+
useOnEvent("flagsUpdated", () => {
235236
setFlags(client.getFlags() ?? {});
236-
// Subscribe to updates
237-
return client.on("flagsUpdated", () => {
238-
setFlags(client.getFlags());
239-
});
240-
}, [client]);
237+
});
241238

242239
return (
243240
<div>

0 commit comments

Comments
 (0)