or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

act-utilities.mdasync-testing.mdcleanup-management.mderror-handling.mdhook-rendering.mdindex.mdserver-side-rendering.md

server-side-rendering.mddocs/

0

# Server-Side Rendering

1

2

Server-specific rendering capabilities for testing hooks in SSR environments with hydration support. This functionality is only available when using the server renderer from `@testing-library/react-hooks/server`.

3

4

## Capabilities

5

6

### Server renderHook

7

8

The server renderer provides the same `renderHook` API as other renderers but with additional server-specific functionality, including the ability to hydrate rendered components.

9

10

```typescript { .api }

11

/**

12

* Renders a React hook in a server environment with hydration support

13

* @param callback - Function that calls the hook to be tested

14

* @param options - Optional configuration including initial props and wrapper component

15

* @returns ServerRenderHookResult with hydrate method and standard utilities

16

*/

17

function renderHook<TProps, TResult>(

18

callback: (props: TProps) => TResult,

19

options?: RenderHookOptions<TProps>

20

): ServerRenderHookResult<TProps, TResult>;

21

22

interface ServerRenderHookResult<TProps, TValue> extends RenderHookResult<TProps, TValue> {

23

/** Hydrate the server-rendered markup on the client */

24

hydrate(): void;

25

}

26

```

27

28

**Usage Examples:**

29

30

```typescript

31

import { renderHook } from "@testing-library/react-hooks/server";

32

import { useState, useEffect } from "react";

33

34

// Server-side hook testing

35

function useServerState(initialValue: string) {

36

const [value, setValue] = useState(initialValue);

37

const [isClient, setIsClient] = useState(false);

38

39

useEffect(() => {

40

setIsClient(true); // This only runs on the client

41

}, []);

42

43

return { value, setValue, isClient };

44

}

45

46

test("server-side rendering", () => {

47

const { result, hydrate } = renderHook(() => useServerState("server"));

48

49

// On the server, useEffect hasn't run yet

50

expect(result.current.value).toBe("server");

51

expect(result.current.isClient).toBe(false);

52

53

// Hydrate on the client

54

hydrate();

55

56

// After hydration, client-side effects run

57

expect(result.current.isClient).toBe(true);

58

});

59

```

60

61

### Hydration Method

62

63

The `hydrate()` method simulates the client-side hydration of server-rendered content, allowing you to test the transition from server to client rendering.

64

65

```typescript { .api }

66

/**

67

* Hydrate the server-rendered markup on the client side

68

* This triggers client-side effects and state updates

69

*/

70

hydrate(): void;

71

```

72

73

**Usage Examples:**

74

75

```typescript

76

function useHydrationEffect() {

77

const [serverData, setServerData] = useState("server-data");

78

const [clientData, setClientData] = useState(null);

79

80

useEffect(() => {

81

// This only runs on the client after hydration

82

setClientData("client-data");

83

}, []);

84

85

return { serverData, clientData };

86

}

87

88

test("hydration effects", () => {

89

const { result, hydrate } = renderHook(() => useHydrationEffect());

90

91

// Server state

92

expect(result.current.serverData).toBe("server-data");

93

expect(result.current.clientData).toBe(null);

94

95

// Hydrate to client

96

hydrate();

97

98

// Client effects have run

99

expect(result.current.clientData).toBe("client-data");

100

});

101

102

// Testing hydration mismatches

103

function useConditionalRender() {

104

const [isClient, setIsClient] = useState(false);

105

106

useEffect(() => {

107

setIsClient(true);

108

}, []);

109

110

// This creates a hydration mismatch

111

return isClient ? "client" : "server";

112

}

113

114

test("hydration mismatch handling", () => {

115

const { result, hydrate } = renderHook(() => useConditionalRender());

116

117

expect(result.current).toBe("server");

118

119

// Hydration will cause a mismatch but should resolve

120

hydrate();

121

122

expect(result.current).toBe("client");

123

});

124

```

125

126

### Server Context Testing

127

128

Testing hooks that depend on server-side context or data fetching scenarios.

129

130

```typescript

131

const ServerContext = React.createContext({ isServer: true });

132

133

function useServerContext() {

134

const context = React.useContext(ServerContext);

135

const [mounted, setMounted] = useState(false);

136

137

useEffect(() => {

138

setMounted(true);

139

}, []);

140

141

return { ...context, mounted };

142

}

143

144

test("server context with hydration", () => {

145

const serverWrapper = ({ children }) => (

146

<ServerContext.Provider value={{ isServer: true }}>

147

{children}

148

</ServerContext.Provider>

149

);

150

151

const clientWrapper = ({ children }) => (

152

<ServerContext.Provider value={{ isServer: false }}>

153

{children}

154

</ServerContext.Provider>

155

);

156

157

const { result, hydrate } = renderHook(() => useServerContext(), {

158

wrapper: serverWrapper

159

});

160

161

// Server state

162

expect(result.current.isServer).toBe(true);

163

expect(result.current.mounted).toBe(false);

164

165

// Hydrate with client context

166

hydrate();

167

168

// Client state after hydration and effects

169

expect(result.current.mounted).toBe(true);

170

});

171

```

172

173

### SSR Data Fetching

174

175

Testing hooks that handle server-side data fetching and client-side rehydration.

176

177

```typescript

178

function useSSRData(url: string) {

179

const [data, setData] = useState(null);

180

const [loading, setLoading] = useState(true);

181

const [isSSR, setIsSSR] = useState(true);

182

183

useEffect(() => {

184

setIsSSR(false);

185

186

if (!data) {

187

// Client-side data fetching

188

fetch(url)

189

.then(response => response.json())

190

.then(data => {

191

setData(data);

192

setLoading(false);

193

});

194

} else {

195

// Data was server-rendered

196

setLoading(false);

197

}

198

}, [url, data]);

199

200

return { data, loading, isSSR };

201

}

202

203

test("SSR data fetching", async () => {

204

// Mock server-rendered data

205

const serverData = { id: 1, name: "Server Data" };

206

207

const { result, hydrate, waitFor } = renderHook(() => {

208

// Simulate server-side data injection

209

const [initialData] = useState(serverData);

210

return useSSRData("/api/data");

211

});

212

213

// Server state with pre-rendered data

214

expect(result.current.isSSR).toBe(true);

215

expect(result.current.loading).toBe(true);

216

217

// Hydrate on client

218

hydrate();

219

220

// Wait for client-side effects

221

await waitFor(() => !result.current.isSSR);

222

223

expect(result.current.isSSR).toBe(false);

224

expect(result.current.loading).toBe(false);

225

});

226

```

227

228

### Error Boundaries in SSR

229

230

Testing error boundaries that behave differently on server vs client.

231

232

```typescript

233

function useSSRErrorBoundary() {

234

const [shouldError, setShouldError] = useState(false);

235

const [isClient, setIsClient] = useState(false);

236

237

useEffect(() => {

238

setIsClient(true);

239

}, []);

240

241

if (shouldError && isClient) {

242

throw new Error("Client-side error");

243

}

244

245

return { shouldError, setShouldError, isClient };

246

}

247

248

test("SSR error boundary behavior", () => {

249

const { result, hydrate } = renderHook(() => useSSRErrorBoundary());

250

251

expect(result.current.isClient).toBe(false);

252

253

// Set error flag on server (no error thrown)

254

act(() => {

255

result.current.setShouldError(true);

256

});

257

258

expect(result.error).toBeUndefined();

259

260

// Hydrate to client (error now thrown)

261

expect(() => {

262

hydrate();

263

}).toThrow("Client-side error");

264

265

expect(result.error).toBeInstanceOf(Error);

266

});

267

```

268

269

### Best Practices for SSR Testing

270

271

**Test Both Server and Client States:**

272

```typescript

273

test("complete SSR lifecycle", () => {

274

const { result, hydrate } = renderHook(() => useMySSRHook());

275

276

// Verify server state

277

expect(result.current.serverProperty).toBeDefined();

278

279

// Hydrate and verify client state

280

hydrate();

281

expect(result.current.clientProperty).toBeDefined();

282

});

283

```

284

285

**Handle Hydration Timing:**

286

```typescript

287

test("hydration timing", async () => {

288

const { result, hydrate, waitForNextUpdate } = renderHook(() => useAsyncSSRHook());

289

290

hydrate();

291

292

// Wait for async effects after hydration

293

await waitForNextUpdate();

294

295

expect(result.current.hydratedData).toBeDefined();

296

});

297

```

298

299

**Mock Server Environment:**

300

```typescript

301

test("mock server environment", () => {

302

// Mock server-specific globals or imports

303

const originalWindow = global.window;

304

delete global.window;

305

306

const { result, hydrate } = renderHook(() => useWindowDetection());

307

308

expect(result.current.hasWindow).toBe(false);

309

310

// Restore window for client hydration

311

global.window = originalWindow;

312

hydrate();

313

314

expect(result.current.hasWindow).toBe(true);

315

});

316

```