0
# Higher-Order Components
1
2
React i18next provides higher-order components (HOCs) for injecting translation functionality into class components and legacy React patterns, as well as server-side rendering support.
3
4
## Capabilities
5
6
### withTranslation HOC
7
8
Higher-order component that injects translation props into wrapped components, providing an alternative to hooks for class components or when hooks aren't suitable.
9
10
```typescript { .api }
11
/**
12
* HOC that injects translation props into wrapped components
13
* @param ns - Namespace(s) for translations
14
* @param options - Configuration options including ref forwarding and key prefix
15
* @returns HOC function that wraps components with translation props
16
*/
17
function withTranslation<
18
Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
19
KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined
20
>(
21
ns?: Ns,
22
options?: {
23
/** Enable ref forwarding to wrapped component */
24
withRef?: boolean;
25
/** Prefix for all translation keys */
26
keyPrefix?: KPrefix;
27
}
28
): <
29
C extends React.ComponentType<React.ComponentProps<any> & WithTranslationProps>
30
>(
31
component: C
32
) => React.ComponentType<Omit<React.ComponentProps<C>, keyof WithTranslation<Ns>> & WithTranslationProps>;
33
34
// Props injected by withTranslation HOC
35
interface WithTranslation<
36
Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
37
KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined
38
> {
39
/** Translation function with type-safe keys */
40
t: TFunction<FallbackNs<Ns>, KPrefix>;
41
/** i18next instance for language changes and configuration */
42
i18n: i18n;
43
/** Indicates if translations are loaded and ready */
44
tReady: boolean;
45
}
46
47
// Props that can be passed to wrapped component
48
interface WithTranslationProps {
49
/** Custom i18next instance */
50
i18n?: i18n;
51
/** Enable React Suspense mode */
52
useSuspense?: boolean;
53
}
54
```
55
56
**Usage Examples:**
57
58
```typescript
59
import { withTranslation, WithTranslation } from "react-i18next";
60
61
// Class component with injected translation props
62
interface Props extends WithTranslation {
63
username: string;
64
}
65
66
class UserProfile extends React.Component<Props> {
67
handleLanguageChange = (lang: string) => {
68
this.props.i18n.changeLanguage(lang);
69
};
70
71
render() {
72
const { t, tReady, username } = this.props;
73
74
if (!tReady) return <div>Loading...</div>;
75
76
return (
77
<div>
78
<h1>{t('profile.title')}</h1>
79
<p>{t('profile.welcome', { name: username })}</p>
80
<button onClick={() => this.handleLanguageChange('es')}>
81
{t('switchLanguage')}
82
</button>
83
</div>
84
);
85
}
86
}
87
88
export default withTranslation('user')(UserProfile);
89
90
// Functional component with HOC (alternative to useTranslation)
91
function Settings({ t, i18n, tReady }: WithTranslation) {
92
if (!tReady) return <div>Loading...</div>;
93
94
return (
95
<div>
96
<h2>{t('settings.title')}</h2>
97
<button onClick={() => i18n.reloadResources()}>
98
{t('settings.reload')}
99
</button>
100
</div>
101
);
102
}
103
104
export default withTranslation('common')(Settings);
105
106
// With namespace and key prefix
107
const TranslatedComponent = withTranslation('dashboard', {
108
keyPrefix: 'widgets'
109
})(function Widget({ t }) {
110
return <div>{t('title')}</div>; // Resolves to 'dashboard:widgets.title'
111
});
112
113
// With ref forwarding
114
class RefComponent extends React.Component {
115
focus() {
116
// Component method
117
}
118
119
render() {
120
const { t } = this.props;
121
return <input placeholder={t('placeholder')} />;
122
}
123
}
124
125
const TranslatedRefComponent = withTranslation('forms', {
126
withRef: true
127
})(RefComponent);
128
129
// Usage with ref
130
function Parent() {
131
const ref = useRef(null);
132
133
return (
134
<div>
135
<TranslatedRefComponent ref={ref} />
136
<button onClick={() => ref.current?.focus()}>
137
Focus Input
138
</button>
139
</div>
140
);
141
}
142
143
// Multiple namespaces
144
const MultiNSComponent = withTranslation(['common', 'user'])(
145
function Profile({ t }) {
146
return (
147
<div>
148
<h1>{t('user:title')}</h1>
149
<button>{t('common:save')}</button>
150
</div>
151
);
152
}
153
);
154
```
155
156
### withSSR HOC
157
158
Higher-order component that adds server-side rendering support to components, including getInitialProps method for data fetching.
159
160
```typescript { .api }
161
/**
162
* HOC providing SSR support with getInitialProps method
163
* @returns HOC function that adds SSR capabilities to wrapped components
164
*/
165
function withSSR(): <Props>(
166
WrappedComponent: React.ComponentType<Props>
167
) => {
168
(props: {
169
initialI18nStore: Resource;
170
initialLanguage: string;
171
} & Props): React.FunctionComponentElement<Props>;
172
getInitialProps: (ctx: unknown) => Promise<{
173
initialI18nStore: Resource;
174
initialLanguage: string;
175
}>;
176
};
177
```
178
179
**Usage Examples:**
180
181
```typescript
182
import { withSSR, WithTranslation, withTranslation } from "react-i18next";
183
184
// Component with SSR support
185
interface PageProps extends WithTranslation {
186
data: any;
187
}
188
189
class HomePage extends React.Component<PageProps> {
190
static async getInitialProps(ctx) {
191
// Fetch page-specific data
192
const data = await fetchPageData(ctx);
193
return { data };
194
}
195
196
render() {
197
const { t, data } = this.props;
198
return (
199
<div>
200
<h1>{t('home.title')}</h1>
201
<div>{data.content}</div>
202
</div>
203
);
204
}
205
}
206
207
// Combine with translation HOC and SSR HOC
208
export default withSSR()(withTranslation('home')(HomePage));
209
210
// Functional component with SSR
211
function ProductPage({ product, t, initialI18nStore, initialLanguage }) {
212
return (
213
<div>
214
<h1>{t('product.title', { name: product.name })}</h1>
215
<p>{t('product.price', { price: product.price })}</p>
216
</div>
217
);
218
}
219
220
ProductPage.getInitialProps = async (ctx) => {
221
const product = await fetchProduct(ctx.query.id);
222
return { product };
223
};
224
225
export default withSSR()(withTranslation('products')(ProductPage));
226
227
// Next.js usage
228
export default function Page(props) {
229
return <HomePage {...props} />;
230
}
231
232
export const getServerSideProps = HomePage.getInitialProps;
233
234
// Usage in Next.js App component
235
class MyApp extends App {
236
static async getInitialProps(appContext) {
237
const appProps = await App.getInitialProps(appContext);
238
239
// Get SSR translation data
240
const { Component } = appContext;
241
if (Component.getInitialProps) {
242
const pageProps = await Component.getInitialProps(appContext.ctx);
243
return { ...appProps, pageProps };
244
}
245
246
return appProps;
247
}
248
249
render() {
250
const { Component, pageProps } = this.props;
251
return <Component {...pageProps} />;
252
}
253
}
254
```
255
256
## HOC Composition and Patterns
257
258
### Combining HOCs
259
260
```typescript
261
// Multiple HOCs composition
262
const EnhancedComponent = withSSR()(
263
withTranslation('namespace')(
264
MyComponent
265
)
266
);
267
268
// With custom display names
269
function MyComponent({ t, customProp }) {
270
return <div>{t('title')} - {customProp}</div>;
271
}
272
273
const Enhanced = withTranslation('custom')(MyComponent);
274
Enhanced.displayName = 'TranslatedMyComponent';
275
276
// Type-safe HOC composition
277
interface ComponentProps {
278
customProp: string;
279
}
280
281
const TypedComponent: React.FC<ComponentProps & WithTranslation> = ({ t, customProp }) => (
282
<div>{t('message')} {customProp}</div>
283
);
284
285
export default withTranslation()(TypedComponent);
286
```
287
288
### Legacy Class Component Integration
289
290
```typescript
291
// Traditional class component with lifecycle methods
292
class LegacyComponent extends React.Component<WithTranslation & { userId: string }> {
293
componentDidMount() {
294
const { i18n, userId } = this.props;
295
296
// Load user-specific translations
297
i18n.loadNamespaces(['user-specific']);
298
}
299
300
componentDidUpdate(prevProps) {
301
const { i18n, userId } = this.props;
302
303
if (prevProps.userId !== userId) {
304
// Reload translations for new user
305
i18n.reloadResources();
306
}
307
}
308
309
render() {
310
const { t, tReady, userId } = this.props;
311
312
if (!tReady) {
313
return <div>{t('loading')}</div>;
314
}
315
316
return (
317
<div>
318
<h1>{t('user.welcome', { id: userId })}</h1>
319
<p>{t('user.status')}</p>
320
</div>
321
);
322
}
323
}
324
325
export default withTranslation(['common', 'user'])(LegacyComponent);
326
```
327
328
## Type Definitions
329
330
```typescript { .api }
331
// Helper types for component props manipulation
332
type $Subtract<T extends K, K> = Omit<T, keyof K>;
333
334
// Fallback namespace resolution
335
type FallbackNs<Ns> = Ns extends undefined
336
? TypeOptions['defaultNS']
337
: Ns extends Namespace
338
? Ns
339
: TypeOptions['defaultNS'];
340
341
// HOC return type helpers
342
type HOCResult<C, InjectedProps> = React.ComponentType<
343
Omit<React.ComponentProps<C>, keyof InjectedProps> & WithTranslationProps
344
>;
345
```
346
347
## Migration from Class Components
348
349
When migrating from HOCs to hooks:
350
351
```typescript
352
// Before: HOC pattern
353
class OldComponent extends React.Component<WithTranslation> {
354
render() {
355
const { t } = this.props;
356
return <div>{t('message')}</div>;
357
}
358
}
359
export default withTranslation()(OldComponent);
360
361
// After: Hook pattern
362
function NewComponent() {
363
const { t } = useTranslation();
364
return <div>{t('message')}</div>;
365
}
366
export default NewComponent;
367
```
368
369
## Performance Considerations
370
371
- HOCs create wrapper components that may affect React DevTools display
372
- Use `withRef: true` when you need to access wrapped component methods
373
- Consider hooks for new components as they have less overhead
374
- HOCs are still valuable for class components and complex composition patterns