>(
+ Target: React.ComponentType,
+ Source: React.ComponentType,
+ displayName: string
+): React.ForwardRefExoticComponent & React.RefAttributes> {
+ // Make sure to hoist statics and forward any refs through from Source to Target
+ // From the React docs:
+ // https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
+ // https://reactjs.org/docs/forwarding-refs.html#forwarding-refs-in-higher-order-components
+ const forwardRef: React.ForwardRefRenderFunction = (props, ref) => ;
+ forwardRef.displayName = `${displayName}(${Source.displayName || Source.name})`;
+ return hoistNonReactStatics<
+ React.ForwardRefExoticComponent & React.RefAttributes>,
+ React.ComponentType
+ >(React.forwardRef(forwardRef), Source);
+}
diff --git a/src/server.ts b/src/server.ts
new file mode 100644
index 0000000..08943b2
--- /dev/null
+++ b/src/server.ts
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2026 Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Server-safe entry point for @optimizely/react-sdk.
+ *
+ * This module can be safely imported in React Server Components (RSC)
+ * as it does not use any client-only React APIs (createContext, hooks, etc.).
+ */
+
+export { createInstance, ReactSDKClient } from './client';
+
+export { OptimizelyDecision } from './utils';
+
+export { default as logOnlyEventDispatcher } from './logOnlyEventDispatcher';
+
+export {
+ logging,
+ errorHandler,
+ setLogger,
+ setLogLevel,
+ enums,
+ eventDispatcher,
+ OptimizelyDecideOption,
+ ActivateListenerPayload,
+ TrackListenerPayload,
+ ListenerPayload,
+ OptimizelySegmentOption,
+} from '@optimizely/optimizely-sdk';
diff --git a/src/utils.spec.tsx b/src/utils.spec.tsx
index 07cad2f..bf27636 100644
--- a/src/utils.spec.tsx
+++ b/src/utils.spec.tsx
@@ -1,5 +1,5 @@
/**
- * Copyright 2024 Optimizely
+ * Copyright 2024, 2026 Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import * as utils from './utils';
+import * as reactUtils from './reactUtils';
import React, { forwardRef } from 'react';
import { render, screen } from '@testing-library/react';
import hoistNonReactStatics from 'hoist-non-react-statics';
@@ -74,7 +75,7 @@ describe('utils', () => {
}
}
- const WrappedComponent = utils.hoistStaticsAndForwardRefs(TestComponent, SourceComponent, 'WrappedComponent');
+ const WrappedComponent = reactUtils.hoistStaticsAndForwardRefs(TestComponent, SourceComponent, 'WrappedComponent');
it('should forward refs and hoist static methods', () => {
const ref = React.createRef();
diff --git a/src/utils.tsx b/src/utils.ts
similarity index 73%
rename from src/utils.tsx
rename to src/utils.ts
index b1f35bc..345204a 100644
--- a/src/utils.tsx
+++ b/src/utils.ts
@@ -1,5 +1,5 @@
/**
- * Copyright 2019, Optimizely
+ * Copyright 2026 Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-import hoistNonReactStatics from 'hoist-non-react-statics';
import * as optimizely from '@optimizely/optimizely-sdk';
-import * as React from 'react';
export type UserInfo = {
id: string | null;
@@ -51,27 +49,6 @@ export function areUsersEqual(user1: UserInfo, user2: UserInfo): boolean {
return true;
}
-export interface AcceptsForwardedRef {
- forwardedRef?: React.Ref;
-}
-
-export function hoistStaticsAndForwardRefs>(
- Target: React.ComponentType,
- Source: React.ComponentType,
- displayName: string
-): React.ForwardRefExoticComponent & React.RefAttributes> {
- // Make sure to hoist statics and forward any refs through from Source to Target
- // From the React docs:
- // https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
- // https://reactjs.org/docs/forwarding-refs.html#forwarding-refs-in-higher-order-components
- const forwardRef: React.ForwardRefRenderFunction = (props, ref) => ;
- forwardRef.displayName = `${displayName}(${Source.displayName || Source.name})`;
- return hoistNonReactStatics<
- React.ForwardRefExoticComponent & React.RefAttributes>,
- React.ComponentType
- >(React.forwardRef(forwardRef), Source);
-}
-
function coerceUnknownAttrsValueForComparison(maybeAttrs: unknown): optimizely.UserAttributes {
if (typeof maybeAttrs === 'object' && maybeAttrs !== null) {
return maybeAttrs as optimizely.UserAttributes;
diff --git a/src/withOptimizely.spec.tsx b/src/withOptimizely.spec.tsx
index f68626e..afc0e6c 100644
--- a/src/withOptimizely.spec.tsx
+++ b/src/withOptimizely.spec.tsx
@@ -70,7 +70,7 @@ describe('withOptimizely', () => {
);
await waitFor(() => expect(optimizelyClient.setUser).toHaveBeenCalledTimes(1));
- expect(optimizelyClient.setUser).toHaveBeenCalledWith({ id: userId, attributes });
+ expect(optimizelyClient.setUser).toHaveBeenCalledWith({ id: userId, attributes }, undefined);
});
});
@@ -84,10 +84,13 @@ describe('withOptimizely', () => {
);
await waitFor(() => expect(optimizelyClient.setUser).toHaveBeenCalledTimes(1));
- expect(optimizelyClient.setUser).toHaveBeenCalledWith({
- id: userId,
- attributes: {},
- });
+ expect(optimizelyClient.setUser).toHaveBeenCalledWith(
+ {
+ id: userId,
+ attributes: {},
+ },
+ undefined
+ );
});
});
@@ -101,10 +104,13 @@ describe('withOptimizely', () => {
);
await waitFor(() => expect(optimizelyClient.setUser).toHaveBeenCalledTimes(1));
- expect(optimizelyClient.setUser).toHaveBeenCalledWith({
- id: userId,
- attributes: {},
- });
+ expect(optimizelyClient.setUser).toHaveBeenCalledWith(
+ {
+ id: userId,
+ attributes: {},
+ },
+ undefined
+ );
});
});
@@ -119,10 +125,13 @@ describe('withOptimizely', () => {
);
await waitFor(() => expect(optimizelyClient.setUser).toHaveBeenCalledTimes(1));
- expect(optimizelyClient.setUser).toHaveBeenCalledWith({
- id: userId,
- attributes,
- });
+ expect(optimizelyClient.setUser).toHaveBeenCalledWith(
+ {
+ id: userId,
+ attributes,
+ },
+ undefined
+ );
});
});
@@ -143,10 +152,13 @@ describe('withOptimizely', () => {
);
await waitFor(() => expect(optimizelyClient.setUser).toHaveBeenCalledTimes(1));
- expect(optimizelyClient.setUser).toHaveBeenCalledWith({
- id: userId,
- attributes,
- });
+ expect(optimizelyClient.setUser).toHaveBeenCalledWith(
+ {
+ id: userId,
+ attributes,
+ },
+ undefined
+ );
});
});
diff --git a/src/withOptimizely.tsx b/src/withOptimizely.tsx
index 0160f17..b822bc2 100644
--- a/src/withOptimizely.tsx
+++ b/src/withOptimizely.tsx
@@ -1,5 +1,5 @@
/**
- * Copyright 2018-2019, Optimizely
+ * Copyright 2018-2019, 2026 Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@ import * as React from 'react';
import { OptimizelyContextConsumer, OptimizelyContextInterface } from './Context';
import { ReactSDKClient } from './client';
-import { hoistStaticsAndForwardRefs } from './utils';
+import { hoistStaticsAndForwardRefs } from './reactUtils';
export interface WithOptimizelyProps {
optimizely: ReactSDKClient | null;