or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-wrapper.mdemotion-integration.mdindex.mdlayout-components.mdstate-management.mdtheme-system.mdtypography-components.mdutility-components.md
tile.json

state-management.mddocs/

0

# State Management

1

2

Utility hooks and functions for managing component state, particularly in controlled/uncontrolled component patterns common in form elements and interactive components.

3

4

## Capabilities

5

6

### Managed State Hook

7

8

Hook for managing component state that can be either controlled (managed by parent) or uncontrolled (managed internally), with automatic detection and warnings for improper usage patterns.

9

10

```typescript { .api }

11

/**

12

* Hook for controlled/uncontrolled component state management

13

* @param controlledValue - Value controlled by parent component

14

* @param defaultValue - Default value for uncontrolled mode

15

* @param onChange - Change handler function

16

* @returns Tuple of current value and change handler

17

*/

18

function useManagedState<V, E = ChangeEvent>(

19

controlledValue: V | undefined,

20

defaultValue: V,

21

onChange: ManagedChangeHandler<V, E> | undefined

22

): [V, ManagedChangeHandler<V, E>];

23

24

/**

25

* Type for change handler functions

26

*/

27

type ManagedChangeHandler<V = string, E = ChangeEvent> = (value: V, event: E) => void;

28

```

29

30

**Usage Examples:**

31

32

```typescript

33

import { useManagedState, ManagedChangeHandler } from "@keystone-ui/core";

34

35

// Custom input component supporting controlled and uncontrolled modes

36

interface CustomInputProps {

37

value?: string;

38

defaultValue?: string;

39

onChange?: ManagedChangeHandler<string>;

40

placeholder?: string;

41

}

42

43

function CustomInput({

44

value: controlledValue,

45

defaultValue = '',

46

onChange,

47

placeholder

48

}: CustomInputProps) {

49

const [value, setValue] = useManagedState(

50

controlledValue,

51

defaultValue,

52

onChange

53

);

54

55

return (

56

<input

57

value={value}

58

onChange={(e) => setValue(e.target.value, e)}

59

placeholder={placeholder}

60

/>

61

);

62

}

63

64

// Controlled usage

65

function ControlledExample() {

66

const [inputValue, setInputValue] = useState('');

67

68

return (

69

<CustomInput

70

value={inputValue}

71

onChange={(value) => setInputValue(value)}

72

/>

73

);

74

}

75

76

// Uncontrolled usage

77

function UncontrolledExample() {

78

return (

79

<CustomInput

80

defaultValue="initial value"

81

onChange={(value) => console.log('Changed to:', value)}

82

/>

83

);

84

}

85

86

// Complex value types

87

interface ToggleProps {

88

checked?: boolean;

89

defaultChecked?: boolean;

90

onChange?: ManagedChangeHandler<boolean, MouseEvent>;

91

}

92

93

function Toggle({ checked: controlledChecked, defaultChecked = false, onChange }: ToggleProps) {

94

const [checked, setChecked] = useManagedState(

95

controlledChecked,

96

defaultChecked,

97

onChange

98

);

99

100

return (

101

<button

102

role="switch"

103

aria-checked={checked}

104

onClick={(e) => setChecked(!checked, e)}

105

>

106

{checked ? 'On' : 'Off'}

107

</button>

108

);

109

}

110

```

111

112

**Key Features:**

113

114

- **Automatic Mode Detection**: Detects controlled vs uncontrolled based on initial prop values

115

- **Development Warnings**: Warns when components switch between controlled/uncontrolled modes

116

- **Type Safety**: Full TypeScript support for value and event types

117

- **Flexible Events**: Supports any event type, not just React ChangeEvents

118

119

## Utility Functions

120

121

### Development Warning Function

122

123

Development-only warning utility that logs messages to console when conditions are met, automatically disabled in production builds.

124

125

```typescript { .api }

126

/**

127

* Logs warning message in development mode only

128

* @param condition - When true, the warning will be logged

129

* @param message - Warning message to display

130

*/

131

function devWarning(condition: boolean, message: string): void;

132

```

133

134

**Usage Examples:**

135

136

```typescript

137

import { devWarning } from "@keystone-ui/core";

138

139

function FormField({ required, value, onChange }) {

140

// Warn about missing required props

141

devWarning(

142

required && !value,

143

'FormField: required field is empty'

144

);

145

146

devWarning(

147

value !== undefined && typeof onChange !== 'function',

148

'FormField: controlled field is missing onChange handler'

149

);

150

151

// Warn about deprecated patterns

152

devWarning(

153

props.hasOwnProperty('color') && props.hasOwnProperty('variant'),

154

'FormField: color prop is deprecated, use variant instead'

155

);

156

157

return <input value={value} onChange={onChange} required={required} />;

158

}

159

160

// Custom hook with validation warnings

161

function useFormValidation(schema, values) {

162

devWarning(

163

!schema,

164

'useFormValidation: schema is required'

165

);

166

167

devWarning(

168

Object.keys(values).length === 0,

169

'useFormValidation: no values provided for validation'

170

);

171

172

// Validation logic...

173

}

174

```

175

176

### ID Generation Utilities

177

178

Utilities for generating unique, accessible IDs with server-side rendering support and compound ID creation.

179

180

```typescript { .api }

181

/**

182

* Hook for generating unique IDs with SSR support

183

* @param idFromProps - Optional ID provided via props

184

* @returns Unique string ID or undefined during SSR

185

*/

186

function useId(idFromProps?: string | null): string | undefined;

187

188

/**

189

* Creates compound ID from multiple inputs, filtering out null/undefined values

190

* @param args - Values to combine into compound ID

191

* @returns Compound ID string with components joined by '--'

192

*/

193

function makeId(...args: (string | number | null | undefined)[]): string;

194

```

195

196

**Usage Examples:**

197

198

```typescript

199

import { useId, makeId } from "@keystone-ui/core";

200

201

// Form field with auto-generated IDs

202

function FormField({

203

id: providedId,

204

label,

205

helperText,

206

errorMessage,

207

required

208

}) {

209

const baseId = useId(providedId);

210

const inputId = baseId;

211

const labelId = makeId(baseId, 'label');

212

const helperId = makeId(baseId, 'helper');

213

const errorId = makeId(baseId, 'error');

214

215

return (

216

<div>

217

<label id={labelId} htmlFor={inputId}>

218

{label} {required && '*'}

219

</label>

220

221

<input

222

id={inputId}

223

aria-labelledby={labelId}

224

aria-describedby={makeId(

225

helperText && helperId,

226

errorMessage && errorId

227

)}

228

aria-invalid={!!errorMessage}

229

aria-required={required}

230

/>

231

232

{helperText && (

233

<div id={helperId} className="helper-text">

234

{helperText}

235

</div>

236

)}

237

238

{errorMessage && (

239

<div id={errorId} className="error-text" role="alert">

240

{errorMessage}

241

</div>

242

)}

243

</div>

244

);

245

}

246

247

// Modal with related IDs

248

function Modal({ titleId: providedTitleId, children }) {

249

const baseId = useId();

250

const titleId = useId(providedTitleId) || makeId(baseId, 'title');

251

const contentId = makeId(baseId, 'content');

252

253

return (

254

<div

255

role="dialog"

256

aria-labelledby={titleId}

257

aria-describedby={contentId}

258

>

259

<h2 id={titleId}>Modal Title</h2>

260

<div id={contentId}>

261

{children}

262

</div>

263

</div>

264

);

265

}

266

267

// Compound IDs with filtering

268

makeId('modal', null, 'content'); // 'modal--content'

269

makeId('field', 123, undefined, 'input'); // 'field--123--input'

270

makeId(null, undefined); // ''

271

```

272

273

### SSR-Safe Layout Effect Hook

274

275

Hook that provides SSR-safe useLayoutEffect functionality, automatically falling back to useEffect in server environments.

276

277

```typescript { .api }

278

/**

279

* SSR-safe version of useLayoutEffect

280

* Falls back to no-op on server, useLayoutEffect on client

281

*/

282

const useSafeLayoutEffect: typeof useLayoutEffect;

283

```

284

285

**Usage Examples:**

286

287

```typescript

288

import { useSafeLayoutEffect } from "@keystone-ui/core";

289

290

// Measuring DOM elements safely

291

function AutoResizeTextarea({ value, onChange }) {

292

const textareaRef = useRef<HTMLTextAreaElement>(null);

293

294

useSafeLayoutEffect(() => {

295

if (textareaRef.current) {

296

// Reset height to get accurate scrollHeight

297

textareaRef.current.style.height = 'auto';

298

// Set height to content height

299

textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;

300

}

301

}, [value]);

302

303

return (

304

<textarea

305

ref={textareaRef}

306

value={value}

307

onChange={onChange}

308

style={{ resize: 'none', overflow: 'hidden' }}

309

/>

310

);

311

}

312

313

// Focus management after render

314

function FocusOnMount({ autoFocus, children }) {

315

const elementRef = useRef<HTMLElement>(null);

316

317

useSafeLayoutEffect(() => {

318

if (autoFocus && elementRef.current) {

319

elementRef.current.focus();

320

}

321

}, [autoFocus]);

322

323

return React.cloneElement(children, { ref: elementRef });

324

}

325

```

326

327

## Advanced State Management Patterns

328

329

### Compound Component State

330

331

Using managed state for compound component patterns:

332

333

```typescript

334

function TabsProvider({ selectedTab, defaultSelectedTab, onTabChange, children }) {

335

const [activeTab, setActiveTab] = useManagedState(

336

selectedTab,

337

defaultSelectedTab || 0,

338

onTabChange

339

);

340

341

const contextValue = {

342

activeTab,

343

setActiveTab,

344

registerTab: useCallback((index) => {

345

// Tab registration logic

346

}, [])

347

};

348

349

return (

350

<TabsContext.Provider value={contextValue}>

351

{children}

352

</TabsContext.Provider>

353

);

354

}

355

356

function Tab({ index, children }) {

357

const { activeTab, setActiveTab } = useContext(TabsContext);

358

const id = useId();

359

const panelId = makeId(id, 'panel');

360

361

return (

362

<button

363

id={id}

364

role="tab"

365

aria-selected={activeTab === index}

366

aria-controls={panelId}

367

onClick={() => setActiveTab(index)}

368

>

369

{children}

370

</button>

371

);

372

}

373

```

374

375

### Form State Management

376

377

Combining multiple managed state hooks for complex forms:

378

379

```typescript

380

function useFormState(initialValues, validationSchema) {

381

const [values, setValues] = useState(initialValues);

382

const [errors, setErrors] = useState({});

383

const [touched, setTouched] = useState({});

384

385

const createFieldProps = useCallback((name) => {

386

const fieldId = useId();

387

const errorId = makeId(fieldId, 'error');

388

389

return {

390

id: fieldId,

391

value: values[name] || '',

392

onChange: (value) => {

393

setValues(prev => ({ ...prev, [name]: value }));

394

// Validate on change...

395

},

396

onBlur: () => {

397

setTouched(prev => ({ ...prev, [name]: true }));

398

// Validate on blur...

399

},

400

'aria-invalid': !!(errors[name] && touched[name]),

401

'aria-describedby': errors[name] && touched[name] ? errorId : undefined

402

};

403

}, [values, errors, touched]);

404

405

return { values, errors, touched, createFieldProps };

406

}

407

```

408

409

## Best Practices

410

411

### State Management Guidelines

412

413

1. **Use useManagedState for dual-mode components** that need to work both controlled and uncontrolled

414

2. **Provide meaningful default values** to ensure consistent initial state

415

3. **Use development warnings** to guide proper component usage

416

4. **Generate stable IDs** for accessibility relationships

417

5. **Handle SSR properly** with useSafeLayoutEffect for DOM measurements

418

419

### Performance Considerations

420

421

- useManagedState creates stable change handlers to prevent unnecessary re-renders

422

- useId generates IDs only once per component instance

423

- devWarning calls are automatically stripped from production builds

424

- useSafeLayoutEffect avoids server/client mismatches