Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ lib
.npmrc
dist/
build/
.build/
.github/prompts/
.rpt2_cache
.env

Expand Down
162 changes: 94 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Refer to the [React SDK's developer documentation](https://docs.developers.optim

For React Native, review the [React Native developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-react-native-sdk).


### Features

- Automatic datafile downloading
Expand All @@ -28,11 +27,7 @@ The React SDK is compatible with `React 16.8.0 +`
### Example

```jsx
import {
createInstance,
OptimizelyProvider,
useDecision,
} from '@optimizely/react-sdk';
import { createInstance, OptimizelyProvider, useDecision } from '@optimizely/react-sdk';

const optimizelyClient = createInstance({
sdkKey: 'your-optimizely-sdk-key',
Expand All @@ -43,8 +38,8 @@ function MyComponent() {
return (
<React.Fragment>
<SearchComponent algorithm={decision.variables.algorithm} />
{ decision.variationKey === 'relevant_first' && <RelevantFirstList /> }
{ decision.variationKey === 'recent_first' && <RecentFirstList /> }
{decision.variationKey === 'relevant_first' && <RelevantFirstList />}
{decision.variationKey === 'recent_first' && <RecentFirstList />}
</React.Fragment>
);
}
Expand All @@ -70,7 +65,8 @@ class App extends React.Component {
npm install @optimizely/react-sdk
```

For **React Native**, installation instruction is bit different. Check out the
For **React Native**, installation instruction is bit different. Check out the

- [Official Installation guide](https://docs.developers.optimizely.com/feature-experimentation/docs/install-sdk-reactnative)
- [Expo React Native Sample App](https://github.com/optimizely/expo-react-native-sdk-sample)

Expand Down Expand Up @@ -155,9 +151,9 @@ function MyComponent() {
const [decision, isClientReady, didTimeout] = useDecision('the-flag');
return (
<React.Fragment>
{ isClientReady && <div>The Component</div> }
{ didTimeout && <div>Default Component</div>}
{ /* If client is not ready and time out has not occured yet, do not render anything */ }
{isClientReady && <div>The Component</div>}
{didTimeout && <div>Default Component</div>}
{/* If client is not ready and time out has not occured yet, do not render anything */}
</React.Fragment>
);
}
Expand Down Expand Up @@ -277,7 +273,7 @@ class MyComp extends React.Component {
constructor(props) {
super(props);
const { optimizely } = this.props;
const decision = optimizely.decide('feat1');
const decision = optimizely.decide('feat1');

this.state = {
decision.enabled,
Expand All @@ -298,9 +294,11 @@ const WrappedMyComponent = withOptimizely(MyComp);
Any component under the `<OptimizelyProvider>` can access the Optimizely `ReactSDKClient` via the `OptimizelyContext` with `useContext`.

_arguments_

- `OptimizelyContext : React.Context<OptimizelyContextInterface>` The Optimizely context initialized in a parent component (or App).

_returns_

- Wrapped object:
- `optimizely : ReactSDKClient` The client object which was passed to the `OptimizelyProvider`
- `isServerSide : boolean` Value that was passed to the `OptimizelyProvider`
Expand All @@ -321,34 +319,33 @@ function MyComponent() {
};
return (
<>
{ decision.enabled && <p>My feature is enabled</p> }
{ !decision.enabled && <p>My feature is disabled</p> }
{ decision.variationKey === 'control-variation' && <p>Current Variation</p> }
{ decision.variationKey === 'experimental-variation' && <p>Better Variation</p> }
{decision.enabled && <p>My feature is enabled</p>}
{!decision.enabled && <p>My feature is disabled</p>}
{decision.variationKey === 'control-variation' && <p>Current Variation</p>}
{decision.variationKey === 'experimental-variation' && <p>Better Variation</p>}
<button onClick={onClick}>Sign Up!</button>
</>
);
}
```

### Tracking

Use the built-in `useTrackEvent` hook to access the `track` method of optimizely instance

```jsx
import { useTrackEvent } from '@optimizely/react-sdk';

function SignupButton() {
const [track, clientReady, didTimeout] = useTrackEvent()
const [track, clientReady, didTimeout] = useTrackEvent();

const handleClick = () => {
if(clientReady) {
track('signup-clicked')
if (clientReady) {
track('signup-clicked');
}
}
};

return (
<button onClick={handleClick}>Signup</button>
)
return <button onClick={handleClick}>Signup</button>;
}
```

Expand Down Expand Up @@ -411,70 +408,99 @@ To rollout or experiment on a feature by user rather than by random percentage,

## Server Side Rendering

Right now server side rendering is possible with a few caveats.
The React SDK supports server-side rendering (SSR). To generate synchronous decisions during SSR, you must pre-fetch the datafile and pass it to `createInstance`. Using `sdkKey` alone is not supported for SSR because it requires an asynchronous network call.

**Caveats**
### Setup

1. You must download the datafile manually and pass in via the `datafile` option. Can not use `sdkKey` to automatically download.
Fetch the datafile on the server, create an Optimizely instance, and wrap your app with `<OptimizelyProvider>`:

2. Rendering of components must be completely synchronous (this is true for all server side rendering), thus the Optimizely SDK assumes that the optimizely client has been instantiated and fired it's `onReady` event already.
```jsx
import { createInstance, OptimizelyProvider, useDecision } from '@optimizely/react-sdk';

### Setting up `<OptimizelyProvider>`
// Pre-fetched datafile (fetching mechanism depends on your framework)
const optimizelyClient = createInstance({
datafile, // must be provided for SSR
sdkKey: 'YOUR_SDK_KEY'
});

Similar to browser side rendering you will need to wrap your app (or portion of the app using Optimizely) in the `<OptimizelyProvider>` component. A new prop
`isServerSide` must be equal to true.
function MyComponent() {
const [decision] = useDecision('flag1');
return decision.enabled ? <p>Feature enabled</p> : <p>Feature disabled</p>;
}

```jsx
<OptimizelyProvider optimizely={optimizely} user={{ id: 'user1' }} isServerSide={true}>
<App />
</OptimizelyProvider>
// Wrap your app with OptimizelyProvider
<OptimizelyProvider optimizely={optimizelyClient} user={{ id: 'user1' }} isServerSide={typeof window === 'undefined'}>
<MyComponent />
</OptimizelyProvider>;
```

All other Optimizely components, such as `<OptimizelyFeature>` and `<OptimizelyExperiment>` can remain the same.
### Configuring the instance for server use

### Full example
Server-side instances are short-lived (created per request) and may not be garbage collected immediately. To avoid unnecessary background work and ensure events are dispatched before the instance is discarded, configure `createInstance` with server-appropriate options:

```jsx
import * as React from 'react';
import * as ReactDOMServer from 'react-dom/server';
import { createInstance, OptimizelyDecideOption } from '@optimizely/react-sdk';

import {
createInstance,
OptimizelyProvider,
useDecision,
} from '@optimizely/react-sdk';
const isServer = typeof window === 'undefined';

const fetch = require('node-fetch');
const optimizelyClient = createInstance({
datafile,
sdkKey: 'YOUR_SDK_KEY'
datafileOptions: { autoUpdate: !isServer },
eventBatchSize: isServer ? 1 : 10,
eventMaxQueueSize: isServer ? 1 : 100,
// Optional: disable decision events on server if they will be sent from the client
defaultDecideOptions: isServer ? [OptimizelyDecideOption.DISABLE_DECISION_EVENT] : [],
});
```

function MyComponent() {
const [decision] = useDecision('flag1');
return (
<React.Fragment>
{ decision.enabled && <p>The feature is enabled</p> }
{ !decision.enabled && <p>The feature is not enabled</p> }
{ decision.variationKey === 'variation1' && <p>Variation 1</p> }
{ decision.variationKey === 'variation2' && <p>Variation 2</p> }
</React.Fragment>
);
}
| Option | Server value | Why |
|---|---|---|
| `datafileOptions.autoUpdate` | `false` | No need to poll for datafile updates on a per-request instance |
| `eventBatchSize` | `1` | Flush events immediately — the instance won't live long enough for a batch to fill |
| `eventMaxQueueSize` | `1` | Prevent event accumulation in a short-lived instance |
| `defaultDecideOptions` | `[DISABLE_DECISION_EVENT]` | Optional — avoids duplicate decision events if the client will also fire them after hydration |

async function main() {
const resp = await fetch('https://cdn.optimizely.com/datafiles/<Your-SDK-Key>.json');
const datafile = await resp.json();
const optimizelyClient = createInstance({
datafile,
### React Server Components

The SDK can also be used directly in React Server Components without `OptimizelyProvider`. Create an instance, set the user, wait for readiness, and make decisions — all within an `async` server component:

```tsx
import { createInstance } from '@optimizely/react-sdk';

export default async function ServerExperiment() {
const client = createInstance({
sdkKey: process.env.OPTIMIZELY_SDK_KEY || '',
});

const output = ReactDOMServer.renderToString(
<OptimizelyProvider optimizely={optimizelyClient} user={{ id: 'user1' }} isServerSide={true}>
<MyComponent />
</OptimizelyProvider>
);
console.log('output', output);
client.setUser({
id: 'user-123',
});

await client.onReady();

const decision = client.decide('flag-1');

client.close();

return decision.enabled
? <h1>Experiment Variation</h1>
: <h1>Control</h1>;
}
main();
```

### Next.js Integration

For detailed Next.js examples covering both App Router and Pages Router patterns, see the [Next.js Integration Guide](docs/nextjs-integration.md).

### Limitations

- **Datafile required** — SSR requires a pre-fetched datafile. Using `sdkKey` alone falls back to a failed decision.
- **Static user only** — User `Promise` is not supported during SSR.
- **ODP segments unavailable** — ODP audience segments require async I/O and are not available during server rendering.

For more details and workarounds, see the [Next.js Integration Guide — Limitations](docs/nextjs-integration.md#limitations).

## Disabled event dispatcher

To disable sending all events to Optimizely's results backend, use the `logOnlyEventDispatcher` when creating a client:
Expand Down
Loading