or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-proxy.mdindex.mdreact-integration.mdutilities.md

react-integration.mddocs/

0

# React Integration

1

2

React hooks optimized for Valtio's proxy system, providing automatic re-rendering only when accessed properties change. The integration offers maximum performance with minimal boilerplate.

3

4

## Capabilities

5

6

### useSnapshot Hook

7

8

Creates a local snapshot that catches changes and triggers re-renders only when accessed parts of the state change. This is the primary hook for reading Valtio state in React components.

9

10

```typescript { .api }

11

/**

12

* Create a local snapshot that catches changes with render optimization

13

* Rule of thumb: read from snapshots, mutate the source

14

* The component will only re-render when accessed parts change

15

* @param proxyObject - The proxy object to create a snapshot from

16

* @param options - Configuration options

17

* @param options.sync - If true, notifications happen synchronously

18

* @returns A wrapped snapshot in a proxy for render optimization

19

*/

20

function useSnapshot<T extends object>(

21

proxyObject: T,

22

options?: { sync?: boolean }

23

): Snapshot<T>;

24

```

25

26

**Usage Examples:**

27

28

```typescript

29

import { proxy, useSnapshot } from "valtio";

30

31

const state = proxy({

32

count: 0,

33

text: "hello",

34

user: { name: "Alice", age: 25 }

35

});

36

37

// Basic usage - only re-renders when count changes

38

function Counter() {

39

const snap = useSnapshot(state);

40

return (

41

<div>

42

{snap.count}

43

<button onClick={() => ++state.count}>+1</button>

44

</div>

45

);

46

}

47

48

// Nested property access - only re-renders when user.name changes

49

function UserName() {

50

const snap = useSnapshot(state);

51

return (

52

<div>

53

Hello, {snap.user.name}!

54

<button onClick={() => state.user.name = "Bob"}>

55

Change Name

56

</button>

57

</div>

58

);

59

}

60

61

// Partial destructuring - only re-renders when user object changes

62

function UserProfile() {

63

const { user } = useSnapshot(state);

64

return (

65

<div>

66

{user.name} ({user.age})

67

</div>

68

);

69

}

70

71

// Synchronous updates for immediate re-renders

72

function ImmediateCounter() {

73

const snap = useSnapshot(state, { sync: true });

74

return <div>{snap.count}</div>;

75

}

76

```

77

78

**Important Notes:**

79

80

- Read from snapshots in render, mutate the original proxy

81

- The component only re-renders when properties you actually access change

82

- Snapshot objects are deeply readonly for type safety

83

- Each `useSnapshot` call creates a new proxy for render optimization

84

85

### useProxy Hook

86

87

Takes a proxy and returns a new proxy that can be used safely in both React render functions and event callbacks. The root reference is replaced on every render, but nested keys remain stable.

88

89

```typescript { .api }

90

/**

91

* Takes a proxy and returns a new proxy for use in both render and callbacks

92

* The root reference is replaced on every render, but keys below it are stable

93

* until they're intentionally mutated

94

* @param proxy - The proxy object to wrap

95

* @param options - Options passed to useSnapshot internally

96

* @returns A new proxy for render and callback usage

97

*/

98

function useProxy<T extends object>(

99

proxy: T,

100

options?: { sync?: boolean }

101

): T;

102

```

103

104

**Usage Examples:**

105

106

```typescript

107

import { proxy, useProxy } from "valtio";

108

109

const globalState = proxy({ count: 0, items: [] });

110

111

// Export custom hook from your store for better ergonomics

112

export const useStore = () => useProxy(globalState);

113

114

// Component usage

115

function Counter() {

116

const store = useStore();

117

118

// Can read in render (like snapshot)

119

const count = store.count;

120

121

// Can mutate in callbacks (like original proxy)

122

const increment = () => { store.count++; };

123

124

return (

125

<div>

126

{count}

127

<button onClick={increment}>+1</button>

128

</div>

129

);

130

}

131

132

// Direct usage without custom hook

133

function TodoList() {

134

const state = useProxy(globalState);

135

136

return (

137

<div>

138

<button onClick={() => state.items.push(`Item ${state.items.length}`)}>

139

Add Item

140

</button>

141

{state.items.map((item, index) => (

142

<div key={index}>{item}</div>

143

))}

144

</div>

145

);

146

}

147

```

148

149

**Benefits over useSnapshot:**

150

151

- Single reference for both reading and writing

152

- No need to import both proxy and snapshot references

153

- More ergonomic for complex interactions

154

- Stable references for nested objects until mutations occur

155

156

**Important Notes:**

157

158

- Uses `useSnapshot` internally for render optimization

159

- Root reference changes on every render (intentional for React optimization)

160

- May not work properly with React Compiler due to intentional render behavior

161

- Best suited for components that both read and write to the same state

162

163

## React Integration Patterns

164

165

### Component State

166

167

```typescript

168

import { proxy, useSnapshot } from "valtio";

169

170

// Local component state

171

function TodoApp() {

172

const [state] = useState(() => proxy({

173

todos: [],

174

filter: 'all'

175

}));

176

177

const snap = useSnapshot(state);

178

179

const addTodo = (text: string) => {

180

state.todos.push({ id: Date.now(), text, done: false });

181

};

182

183

return (

184

<div>

185

{snap.todos.map(todo => (

186

<TodoItem key={todo.id} todo={todo} />

187

))}

188

</div>

189

);

190

}

191

```

192

193

### Global State

194

195

```typescript

196

import { proxy, useSnapshot } from "valtio";

197

198

// Global state

199

export const appState = proxy({

200

user: null,

201

theme: 'light',

202

notifications: []

203

});

204

205

// Multiple components can use the same state

206

function Header() {

207

const { user, theme } = useSnapshot(appState);

208

return (

209

<header className={theme}>

210

Welcome, {user?.name}

211

</header>

212

);

213

}

214

215

function Settings() {

216

const { theme } = useSnapshot(appState);

217

return (

218

<button onClick={() => appState.theme = theme === 'light' ? 'dark' : 'light'}>

219

Toggle Theme

220

</button>

221

);

222

}

223

```

224

225

### Conditional Re-rendering

226

227

```typescript

228

function OptimizedComponent() {

229

const snap = useSnapshot(state);

230

231

// Only accesses count, so only re-renders when count changes

232

if (snap.count > 10) {

233

return <div>Count is high: {snap.count}</div>;

234

}

235

236

// If condition is false, text is never accessed

237

// Component won't re-render when text changes

238

return <div>Count is low, text: {snap.text}</div>;

239

}

240

```

241

242

## Types

243

244

```typescript { .api }

245

type Snapshot<T> = T extends { $$valtioSnapshot: infer S }

246

? S

247

: T extends SnapshotIgnore

248

? T

249

: T extends object

250

? { readonly [K in keyof T]: Snapshot<T[K]> }

251

: T;

252

253

interface Options {

254

sync?: boolean;

255

}

256

```