Skip to content

Commit 13baa08

Browse files
authored
chore: add a minimal demo example (#212)
1 parent a8810cd commit 13baa08

File tree

15 files changed

+1232
-1
lines changed

15 files changed

+1232
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/examples/basic-app/node_modules
2+
/examples/basic-app/.env
13
/node_modules/
24
/dist
35
yarn-error.log

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ npm install @unleash/proxy-client-react unleash-proxy-client
1212
yarn add @unleash/proxy-client-react unleash-proxy-client
1313
```
1414

15+
## Example application
16+
17+
To see the SDK in action, explore the [`examples/basic-app`](examples/basic-app/README.md) directory.
18+
It contains a minimal Vite + React setup that connects to Unleash, evaluates a feature toggle,
19+
and updates the evaluation context dynamically at runtime. The README in that folder includes step-by-step instructions
20+
for running the example.
21+
1522
# How to use
1623

1724
This library uses the core [unleash-js-sdk](https://github.com/Unleash/unleash-js-sdk) client as a base.

examples/basic-app/.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
VITE_UNLEASH_URL=http://localhost:4242/api/frontend
2+
VITE_UNLEASH_CLIENT_KEY=YOUR_FRONTEND_TOKEN
3+
VITE_UNLEASH_APP_NAME=react-example-app
4+
VITE_UNLEASH_REFRESH_INTERVAL=15
5+
VITE_UNLEASH_ENVIRONMENT=development
6+
VITE_EXAMPLE_DEFAULT_USER=guest

examples/basic-app/README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Unleash React SDK example
2+
3+
This directory contains a small Vite + React application that consumes
4+
`@unleash/proxy-client-react`. It connects to the Unleash Frontend API and
5+
shows how to:
6+
7+
- bootstrap the SDK with `FlagProvider`
8+
- evaluate a toggle with `useFlag`
9+
- inspect variants with `useVariant`
10+
- update the evaluation context with `useUnleashContext`
11+
- react to the client status via `useFlagsStatus`
12+
13+
## Prerequisites
14+
15+
You need a running [Unleash Frontend API](https://docs.getunleash.io/reference/front-end-api)
16+
or [Unleash Edge](https://docs.getunleash.io/reference/unleash-edge) along with an
17+
appropriate frontend token.
18+
19+
## Local setup
20+
21+
From the repository root, install the dependencies used by the example:
22+
23+
```bash
24+
cd examples/basic-app
25+
yarn install
26+
```
27+
28+
The Vite configuration aliases `@unleash/proxy-client-react` to the local `src`
29+
folder, so you do not need to build the SDK before running the demo. Swap this
30+
alias for the npm package if you copy the example into another project.
31+
32+
Create a `.env` file with your connection details:
33+
34+
```bash
35+
cp .env.example .env
36+
```
37+
38+
and edit the file to match your environment:
39+
40+
```env
41+
VITE_UNLEASH_URL=https://<your-unleash-domain>/api/frontend
42+
VITE_UNLEASH_CLIENT_KEY=<frontend-or-pretrusted-token>
43+
VITE_UNLEASH_APP_NAME=react-example-app
44+
VITE_UNLEASH_REFRESH_INTERVAL=15
45+
VITE_UNLEASH_ENVIRONMENT=development
46+
```
47+
48+
> The app evaluates the `demo-app.simple-toggle` flag by default. Make sure the
49+
> toggle exists and is available to the token you supplied.
50+
51+
Start the Vite dev server:
52+
53+
```bash
54+
yarn dev
55+
```
56+
57+
Open the printed URL (defaults to http://localhost:5173) in your browser. The
58+
UI renders the current status for the configured toggle and exposes a small
59+
form that lets you swap the `userId` used for evaluations.

examples/basic-app/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Unleash React SDK Example</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/main.tsx"></script>
11+
</body>
12+
</html>

examples/basic-app/package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "unleash-react-sdk-example",
3+
"private": true,
4+
"type": "module",
5+
"version": "0.0.0",
6+
"scripts": {
7+
"dev": "vite --open",
8+
"build": "vite build",
9+
"preview": "vite preview"
10+
},
11+
"dependencies": {
12+
"@unleash/proxy-client-react": "file:../..",
13+
"react": "^18.3.1",
14+
"react-dom": "^18.3.1",
15+
"unleash-proxy-client": "^3.7.3"
16+
},
17+
"devDependencies": {
18+
"@types/react": "^18.3.3",
19+
"@types/react-dom": "^18.3.0",
20+
"@vitejs/plugin-react": "^4.3.1",
21+
"typescript": "^5.5.4",
22+
"vite": "^5.4.0"
23+
}
24+
}

examples/basic-app/src/App.css

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
:root {
2+
color-scheme: light dark;
3+
font-synthesis: none;
4+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
5+
margin: 0;
6+
padding: 0;
7+
background: #f6f6f6;
8+
}
9+
10+
body {
11+
margin: 0;
12+
min-height: 100vh;
13+
background: radial-gradient(circle at top, #ffffff 0%, #f0f4ff 100%);
14+
}
15+
16+
.app {
17+
max-width: 720px;
18+
margin: 0 auto;
19+
padding: 2rem 1.5rem 4rem;
20+
color: #1d2130;
21+
}
22+
23+
.app--loading {
24+
display: flex;
25+
flex-direction: column;
26+
gap: 1rem;
27+
}
28+
29+
header {
30+
display: flex;
31+
flex-direction: column;
32+
gap: 0.75rem;
33+
margin-bottom: 2rem;
34+
}
35+
36+
.card {
37+
background: rgba(255, 255, 255, 0.88);
38+
border-radius: 16px;
39+
padding: 1.5rem;
40+
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.08);
41+
backdrop-filter: blur(12px);
42+
margin-bottom: 1.5rem;
43+
}
44+
45+
.card h2 {
46+
margin-top: 0;
47+
}
48+
49+
.toggle {
50+
padding: 0.5rem 0.75rem;
51+
border-radius: 12px;
52+
display: inline-block;
53+
font-weight: 600;
54+
}
55+
56+
.toggle--on {
57+
background: rgba(34, 197, 94, 0.16);
58+
color: #0d6831;
59+
}
60+
61+
.toggle--off {
62+
background: rgba(248, 113, 113, 0.18);
63+
color: #991b1b;
64+
}
65+
66+
.context-form {
67+
display: flex;
68+
flex-direction: column;
69+
gap: 0.75rem;
70+
}
71+
72+
.context-form__row {
73+
display: flex;
74+
gap: 0.5rem;
75+
}
76+
77+
.context-form input {
78+
flex: 1;
79+
border-radius: 10px;
80+
border: 1px solid rgba(148, 163, 184, 0.6);
81+
padding: 0.6rem 0.9rem;
82+
font-size: 1rem;
83+
}
84+
85+
.context-form button {
86+
border: none;
87+
border-radius: 10px;
88+
padding: 0.6rem 1.2rem;
89+
font-size: 1rem;
90+
background: #2563eb;
91+
color: white;
92+
cursor: pointer;
93+
}
94+
95+
.context-form button:disabled {
96+
opacity: 0.6;
97+
cursor: not-allowed;
98+
}
99+
100+
.context-form__error {
101+
color: #991b1b;
102+
margin: 0;
103+
}
104+
105+
pre {
106+
background: rgba(15, 23, 42, 0.08);
107+
border-radius: 12px;
108+
padding: 1rem;
109+
overflow: auto;
110+
}

examples/basic-app/src/App.tsx

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { FormEvent, useState } from 'react';
2+
import {
3+
useFlag,
4+
useFlagsStatus,
5+
useUnleashContext,
6+
useVariant
7+
} from '@unleash/proxy-client-react';
8+
import './App.css';
9+
10+
const TOGGLE_NAME = 'demo-app.simple-toggle';
11+
12+
const VariantDetails = ({ toggleName }: { toggleName: string }) => {
13+
const variant = useVariant(toggleName);
14+
15+
if (!variant?.enabled) {
16+
return (
17+
<p>
18+
Variant is not enabled for <code>{toggleName}</code>.
19+
</p>
20+
);
21+
}
22+
23+
return (
24+
<>
25+
<p>
26+
<strong>Variant name:</strong> {variant.name}
27+
</p>
28+
{variant.payload && (
29+
<p>
30+
<strong>Payload:</strong> {JSON.stringify(variant.payload)}
31+
</p>
32+
)}
33+
</>
34+
);
35+
};
36+
37+
const ToggleStatus = ({ toggleName }: { toggleName: string }) => {
38+
const enabled = useFlag(toggleName);
39+
40+
return (
41+
<p className={`toggle toggle--${enabled ? 'on' : 'off'}`}>
42+
Feature <code>{toggleName}</code> is{' '}
43+
<strong>{enabled ? 'enabled' : 'disabled'}</strong> for the current user.
44+
</p>
45+
);
46+
};
47+
48+
const ContextForm = ({
49+
userId,
50+
onSubmit
51+
}: {
52+
userId: string;
53+
onSubmit: (nextUserId: string) => Promise<void>;
54+
}) => {
55+
const [value, setValue] = useState(userId);
56+
const [isSubmitting, setIsSubmitting] = useState(false);
57+
const [error, setError] = useState<string | null>(null);
58+
59+
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
60+
event.preventDefault();
61+
if (!value) {
62+
setError('Provide a user id before updating the context.');
63+
return;
64+
}
65+
if (value === userId) {
66+
setError('The provided user id matches the current context.');
67+
return;
68+
}
69+
70+
setError(null);
71+
setIsSubmitting(true);
72+
try {
73+
await onSubmit(value);
74+
} catch (err) {
75+
setError(
76+
err instanceof Error ? err.message : 'Failed to update the Unleash context.'
77+
);
78+
} finally {
79+
setIsSubmitting(false);
80+
}
81+
};
82+
83+
return (
84+
<form className="context-form" onSubmit={handleSubmit}>
85+
<label htmlFor="userId">Simulate another user</label>
86+
<div className="context-form__row">
87+
<input
88+
id="userId"
89+
name="userId"
90+
value={value}
91+
onChange={event => {
92+
setError(null);
93+
setValue(event.target.value);
94+
}}
95+
placeholder="e.g. [email protected]"
96+
/>
97+
<button type="submit" disabled={isSubmitting}>
98+
{isSubmitting ? 'Updating…' : 'Update context'}
99+
</button>
100+
</div>
101+
{error && <p className="context-form__error">{error}</p>}
102+
</form>
103+
);
104+
};
105+
106+
const App = () => {
107+
const { flagsReady, flagsError } = useFlagsStatus();
108+
const [currentUserId, setCurrentUserId] = useState<string>(
109+
import.meta.env.VITE_EXAMPLE_DEFAULT_USER ?? 'guest'
110+
);
111+
const updateContext = useUnleashContext();
112+
113+
const handleContextUpdate = async (nextUserId: string) => {
114+
await updateContext({ userId: nextUserId });
115+
setCurrentUserId(nextUserId);
116+
};
117+
118+
if (!flagsReady) {
119+
return (
120+
<main className="app app--loading">
121+
<h1>Unleash React SDK Example</h1>
122+
<p>Fetching feature toggles…</p>
123+
</main>
124+
);
125+
}
126+
127+
return (
128+
<main className="app">
129+
<header>
130+
<h1>Unleash React SDK Example</h1>
131+
<p>
132+
Connected to the Unleash Frontend API configured in{' '}
133+
<code>.env</code>. The example evaluates the{' '}
134+
<code>{TOGGLE_NAME}</code> toggle.
135+
</p>
136+
</header>
137+
138+
{flagsError && (
139+
<section className="card">
140+
<h2>Latest client error</h2>
141+
<pre>{JSON.stringify(flagsError, null, 2)}</pre>
142+
</section>
143+
)}
144+
145+
<section className="card">
146+
<h2>Toggle status</h2>
147+
<ToggleStatus toggleName={TOGGLE_NAME} />
148+
<VariantDetails toggleName={TOGGLE_NAME} />
149+
</section>
150+
151+
<section className="card">
152+
<h2>Current context</h2>
153+
<p>
154+
Evaluating toggles for <code>{currentUserId}</code>.
155+
</p>
156+
<ContextForm userId={currentUserId} onSubmit={handleContextUpdate} />
157+
</section>
158+
</main>
159+
);
160+
};
161+
162+
export default App;

0 commit comments

Comments
 (0)