or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

compat.mdcomponents.mdcontext.mdcore.mddevtools.mdhooks.mdindex.mdjsx-runtime.mdtesting.md

context.mddocs/

0

# Context API

1

2

Provider/consumer pattern for sharing data across component hierarchies without prop drilling. Preact's Context API provides efficient state management for deeply nested components.

3

4

## Capabilities

5

6

### Context Creation

7

8

Creates context objects that enable data sharing between components at different levels of the component tree.

9

10

```typescript { .api }

11

/**

12

* Creates a new context with a default value

13

* @param defaultValue - Default value used when no Provider is found

14

* @returns Context object with Provider and Consumer components

15

*/

16

function createContext<T>(defaultValue: T): Context<T>;

17

18

/**

19

* Context object interface

20

*/

21

interface Context<T> {

22

Provider: ComponentType<ProviderProps<T>>;

23

Consumer: ComponentType<ConsumerProps<T>>;

24

displayName?: string;

25

}

26

27

interface ProviderProps<T> {

28

value: T;

29

children?: ComponentChildren;

30

}

31

32

interface ConsumerProps<T> {

33

children: (value: T) => ComponentChildren;

34

}

35

36

// Type aliases for compatibility

37

interface PreactContext<T> extends Context<T> {}

38

interface PreactProvider<T> extends Provider<T> {}

39

interface PreactConsumer<T> extends Consumer<T> {}

40

41

/**

42

* Extract context value type from Context

43

*/

44

type ContextType<C extends Context<any>> = C extends Context<infer T> ? T : never;

45

```

46

47

**Usage Examples:**

48

49

```typescript

50

import { createContext, createElement, useState } from "preact";

51

import { useContext } from "preact/hooks";

52

53

// Theme context

54

interface ThemeContextValue {

55

theme: 'light' | 'dark';

56

toggleTheme: () => void;

57

}

58

59

const ThemeContext = createContext<ThemeContextValue>({

60

theme: 'light',

61

toggleTheme: () => {}

62

});

63

64

// Theme provider component

65

function ThemeProvider({ children }: { children: ComponentChildren }) {

66

const [theme, setTheme] = useState<'light' | 'dark'>('light');

67

68

const toggleTheme = () => {

69

setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');

70

};

71

72

const value = { theme, toggleTheme };

73

74

return createElement(ThemeContext.Provider, { value }, children);

75

}

76

77

// Component using context with hook

78

function ThemedButton() {

79

const { theme, toggleTheme } = useContext(ThemeContext);

80

81

return createElement("button", {

82

onClick: toggleTheme,

83

style: {

84

backgroundColor: theme === 'light' ? '#fff' : '#333',

85

color: theme === 'light' ? '#333' : '#fff',

86

border: `1px solid ${theme === 'light' ? '#333' : '#fff'}`

87

}

88

}, `Switch to ${theme === 'light' ? 'dark' : 'light'} theme`);

89

}

90

91

// Component using context with Consumer

92

function ThemedText() {

93

return createElement(ThemeContext.Consumer, null, ({ theme }) =>

94

createElement("p", {

95

style: {

96

color: theme === 'light' ? '#333' : '#fff'

97

}

98

}, `Current theme: ${theme}`)

99

);

100

}

101

102

// App with theme provider

103

function App() {

104

return createElement(ThemeProvider, null,

105

createElement("div", null,

106

createElement("h1", null, "Themed App"),

107

createElement(ThemedButton),

108

createElement(ThemedText)

109

)

110

);

111

}

112

```

113

114

### Provider Component

115

116

Component that provides context values to descendant components.

117

118

```typescript { .api }

119

/**

120

* Provider component interface

121

*/

122

interface Provider<T> extends FunctionComponent<{

123

value: T;

124

children?: ComponentChildren;

125

}> {}

126

127

/**

128

* Provider component that makes context value available to descendants

129

* Automatically included in Context object from createContext

130

*/

131

```

132

133

**Usage Examples:**

134

135

```typescript

136

import { createContext, createElement, useState } from "preact";

137

138

// User authentication context

139

interface User {

140

id: number;

141

name: string;

142

email: string;

143

}

144

145

interface AuthContextValue {

146

user: User | null;

147

login: (user: User) => void;

148

logout: () => void;

149

isAuthenticated: boolean;

150

}

151

152

const AuthContext = createContext<AuthContextValue>({

153

user: null,

154

login: () => {},

155

logout: () => {},

156

isAuthenticated: false

157

});

158

159

// Authentication provider

160

function AuthProvider({ children }: { children: ComponentChildren }) {

161

const [user, setUser] = useState<User | null>(null);

162

163

const login = (userData: User) => {

164

setUser(userData);

165

localStorage.setItem('user', JSON.stringify(userData));

166

};

167

168

const logout = () => {

169

setUser(null);

170

localStorage.removeItem('user');

171

};

172

173

const contextValue: AuthContextValue = {

174

user,

175

login,

176

logout,

177

isAuthenticated: user !== null

178

};

179

180

return createElement(AuthContext.Provider, { value: contextValue }, children);

181

}

182

183

// Multiple nested providers

184

function AppProviders({ children }: { children: ComponentChildren }) {

185

return createElement(AuthProvider, null,

186

createElement(ThemeProvider, null,

187

createElement(LocaleProvider, null,

188

children

189

)

190

)

191

);

192

}

193

194

// Provider with complex state management

195

interface CartItem {

196

id: number;

197

name: string;

198

price: number;

199

quantity: number;

200

}

201

202

interface CartContextValue {

203

items: CartItem[];

204

addItem: (item: Omit<CartItem, 'quantity'>) => void;

205

removeItem: (id: number) => void;

206

updateQuantity: (id: number, quantity: number) => void;

207

total: number;

208

itemCount: number;

209

}

210

211

const CartContext = createContext<CartContextValue>({

212

items: [],

213

addItem: () => {},

214

removeItem: () => {},

215

updateQuantity: () => {},

216

total: 0,

217

itemCount: 0

218

});

219

220

function CartProvider({ children }: { children: ComponentChildren }) {

221

const [items, setItems] = useState<CartItem[]>([]);

222

223

const addItem = (newItem: Omit<CartItem, 'quantity'>) => {

224

setItems(prevItems => {

225

const existingItem = prevItems.find(item => item.id === newItem.id);

226

if (existingItem) {

227

return prevItems.map(item =>

228

item.id === newItem.id

229

? { ...item, quantity: item.quantity + 1 }

230

: item

231

);

232

}

233

return [...prevItems, { ...newItem, quantity: 1 }];

234

});

235

};

236

237

const removeItem = (id: number) => {

238

setItems(prevItems => prevItems.filter(item => item.id !== id));

239

};

240

241

const updateQuantity = (id: number, quantity: number) => {

242

if (quantity <= 0) {

243

removeItem(id);

244

return;

245

}

246

247

setItems(prevItems =>

248

prevItems.map(item =>

249

item.id === id ? { ...item, quantity } : item

250

)

251

);

252

};

253

254

const total = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);

255

const itemCount = items.reduce((count, item) => count + item.quantity, 0);

256

257

const contextValue: CartContextValue = {

258

items,

259

addItem,

260

removeItem,

261

updateQuantity,

262

total,

263

itemCount

264

};

265

266

return createElement(CartContext.Provider, { value: contextValue }, children);

267

}

268

```

269

270

### Consumer Component

271

272

Component that consumes context values using render props pattern.

273

274

```typescript { .api }

275

/**

276

* Consumer component interface

277

*/

278

interface Consumer<T> extends FunctionComponent<{

279

children: (value: T) => ComponentChildren;

280

}> {}

281

282

/**

283

* Consumer component that provides context value to render function

284

* Automatically included in Context object from createContext

285

*/

286

```

287

288

**Usage Examples:**

289

290

```typescript

291

import { createContext, createElement } from "preact";

292

293

// Using Consumer with render props

294

function UserProfile() {

295

return createElement(AuthContext.Consumer, null, ({ user, isAuthenticated }) => {

296

if (!isAuthenticated) {

297

return createElement("div", null, "Please log in");

298

}

299

300

return createElement("div", { className: "user-profile" },

301

createElement("h2", null, user.name),

302

createElement("p", null, user.email),

303

createElement("img", {

304

src: `https://api.dicebear.com/7.x/initials/svg?seed=${user.name}`,

305

alt: `${user.name}'s avatar`

306

})

307

);

308

});

309

}

310

311

// Consumer with conditional rendering

312

function ShoppingCartIcon() {

313

return createElement(CartContext.Consumer, null, ({ itemCount }) =>

314

createElement("div", { className: "cart-icon" },

315

createElement("span", null, "๐Ÿ›’"),

316

itemCount > 0 && createElement("span", { className: "badge" }, itemCount)

317

)

318

);

319

}

320

321

// Nested consumers

322

function UserDashboard() {

323

return createElement(AuthContext.Consumer, null, ({ user, isAuthenticated }) =>

324

createElement(ThemeContext.Consumer, null, ({ theme }) =>

325

createElement(CartContext.Consumer, null, ({ total, itemCount }) => {

326

if (!isAuthenticated) {

327

return createElement("div", null, "Access denied");

328

}

329

330

return createElement("div", {

331

className: `dashboard theme-${theme}`

332

},

333

createElement("h1", null, `Welcome, ${user.name}!`),

334

createElement("p", null, `Cart: ${itemCount} items ($${total.toFixed(2)})`),

335

createElement("p", null, `Theme: ${theme}`)

336

);

337

})

338

)

339

);

340

}

341

342

// Consumer with error handling

343

function SafeConsumer() {

344

return createElement(AuthContext.Consumer, null, (contextValue) => {

345

try {

346

if (!contextValue) {

347

return createElement("div", null, "Context not available");

348

}

349

350

const { user, isAuthenticated } = contextValue;

351

352

return createElement("div", null,

353

isAuthenticated

354

? `Hello, ${user?.name}`

355

: "Not logged in"

356

);

357

} catch (error) {

358

return createElement("div", null, "Error loading user data");

359

}

360

});

361

}

362

```

363

364

### Context Best Practices

365

366

Patterns and utilities for effective context usage.

367

368

```typescript { .api }

369

/**

370

* Custom hook pattern for context consumption

371

* Provides better error handling and type safety

372

*/

373

function useCustomContext<T>(context: Context<T>, errorMessage?: string): T {

374

const contextValue = useContext(context);

375

376

if (contextValue === undefined) {

377

throw new Error(errorMessage || 'useCustomContext must be used within a Provider');

378

}

379

380

return contextValue;

381

}

382

```

383

384

**Usage Examples:**

385

386

```typescript

387

import { createContext, createElement, useContext } from "preact";

388

389

// Custom hook for safer context consumption

390

function useAuth() {

391

const context = useContext(AuthContext);

392

393

if (!context) {

394

throw new Error('useAuth must be used within an AuthProvider');

395

}

396

397

return context;

398

}

399

400

function useTheme() {

401

const context = useContext(ThemeContext);

402

403

if (!context) {

404

throw new Error('useTheme must be used within a ThemeProvider');

405

}

406

407

return context;

408

}

409

410

// Context with reducer pattern

411

interface AppState {

412

user: User | null;

413

theme: 'light' | 'dark';

414

locale: string;

415

}

416

417

type AppAction =

418

| { type: 'SET_USER'; user: User | null }

419

| { type: 'TOGGLE_THEME' }

420

| { type: 'SET_LOCALE'; locale: string };

421

422

function appReducer(state: AppState, action: AppAction): AppState {

423

switch (action.type) {

424

case 'SET_USER':

425

return { ...state, user: action.user };

426

case 'TOGGLE_THEME':

427

return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };

428

case 'SET_LOCALE':

429

return { ...state, locale: action.locale };

430

default:

431

return state;

432

}

433

}

434

435

interface AppContextValue {

436

state: AppState;

437

dispatch: (action: AppAction) => void;

438

}

439

440

const AppContext = createContext<AppContextValue | null>(null);

441

442

function AppProvider({ children }: { children: ComponentChildren }) {

443

const [state, dispatch] = useReducer(appReducer, {

444

user: null,

445

theme: 'light',

446

locale: 'en'

447

});

448

449

return createElement(AppContext.Provider, {

450

value: { state, dispatch }

451

}, children);

452

}

453

454

function useApp() {

455

const context = useContext(AppContext);

456

457

if (!context) {

458

throw new Error('useApp must be used within an AppProvider');

459

}

460

461

return context;

462

}

463

464

// Component using the app context

465

function AppHeader() {

466

const { state, dispatch } = useApp();

467

468

return createElement("header", {

469

className: `header theme-${state.theme}`

470

},

471

createElement("h1", null, "My App"),

472

state.user && createElement("span", null, `Welcome, ${state.user.name}`),

473

createElement("button", {

474

onClick: () => dispatch({ type: 'TOGGLE_THEME' })

475

}, `Switch to ${state.theme === 'light' ? 'dark' : 'light'} theme`)

476

);

477

}

478

479

// Context composition pattern

480

function Providers({ children }: { children: ComponentChildren }) {

481

return createElement(AppProvider, null,

482

createElement(AuthProvider, null,

483

createElement(CartProvider, null,

484

children

485

)

486

)

487

);

488

}

489

```