Skip to content

Commit

Permalink
feat(RSC): Better error handling for missing context (#508)
Browse files Browse the repository at this point in the history
  • Loading branch information
amannn authored Sep 15, 2023
1 parent 0aa60ad commit 106c098
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/next-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
"size-limit": [
{
"path": "dist/production/index.js",
"limit": "13.61 KB"
"limit": "13.62 KB"
},
{
"path": "dist/production/navigation.js",
Expand Down
28 changes: 28 additions & 0 deletions packages/next-intl/src/react-client/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,38 @@
* supported in all Next.js versions that are supported.
*/

import {
useTranslations as base_useTranslations,
useFormatter as base_useFormatter
} from 'use-intl';
import Link from './Link';

export * from 'use-intl';

// eslint-disable-next-line @typescript-eslint/ban-types
function callHook(name: string, hook: Function) {
return (...args: Array<unknown>) => {
try {
return hook(...args);
} catch (e) {
throw new Error(
process.env.NODE_ENV !== 'production'
? `Failed to call \`${name}\`, because the context from \`NextIntlClientProvider\` was not found.\n\nLearn more about this error here: https://next-intl-docs.vercel.app/docs/environments/server-client-components#missing-context`
: undefined
);
}
};
}

export const useTranslations = callHook(
'useTranslations',
base_useTranslations
) as typeof base_useTranslations;
export const useFormatter = callHook(
'useFormatter',
base_useFormatter
) as typeof base_useFormatter;

// Replace `useLocale` export from `use-intl`
export {default as useLocale} from './useLocale';

Expand Down
24 changes: 24 additions & 0 deletions packages/next-intl/test/react-client/useFormatter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {render, screen} from '@testing-library/react';
import React from 'react';
import {it, expect} from 'vitest';
import {useFormatter, NextIntlClientProvider} from '../../src';

function Component() {
const format = useFormatter();
return <>{format.number(1)}</>;
}

it('provides a helpful error message when no provider is found', () => {
expect(() => render(<Component />)).toThrow(
/Failed to call `useFormatter`, because the context from `NextIntlClientProvider` was not found\./
);
});

it('works with a provider', () => {
render(
<NextIntlClientProvider locale="en">
<Component />
</NextIntlClientProvider>
);
screen.getByText('1');
});
29 changes: 29 additions & 0 deletions packages/next-intl/test/react-client/useNow.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {render, screen} from '@testing-library/react';
import React from 'react';
import {it} from 'vitest';
import {useNow, NextIntlClientProvider} from '../../src';

function Component() {
const now = useNow();
return <>{now.toISOString()}</>;
}

it('works without a provider', () => {
render(
<NextIntlClientProvider locale="en">
<Component />
</NextIntlClientProvider>
);
});

it('works with a provider', () => {
render(
<NextIntlClientProvider
locale="en"
now={new Date('2021-01-01T00:00:00.000Z')}
>
<Component />
</NextIntlClientProvider>
);
screen.getByText('2021-01-01T00:00:00.000Z');
});
26 changes: 26 additions & 0 deletions packages/next-intl/test/react-client/useTimeZone.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {render, screen} from '@testing-library/react';
import React from 'react';
import {it} from 'vitest';
import {useTimeZone, NextIntlClientProvider} from '../../src';

function Component() {
const timeZone = useTimeZone();
return <>{timeZone}</>;
}

it('works without a provider', () => {
render(
<NextIntlClientProvider locale="en">
<Component />
</NextIntlClientProvider>
);
});

it('works with a provider', () => {
render(
<NextIntlClientProvider locale="en" timeZone="America/New_York">
<Component />
</NextIntlClientProvider>
);
screen.getByText('America/New_York');
});
35 changes: 35 additions & 0 deletions packages/next-intl/test/react-client/useTranslations.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {render, screen} from '@testing-library/react';
import React from 'react';
import {it, expect, vi} from 'vitest';
import {useTranslations, NextIntlClientProvider} from '../../src';

function Component() {
const t = useTranslations('Component');
return <>{t('test')}</>;
}

it('provides a helpful error message when no provider is found', () => {
expect(() => render(<Component />)).toThrow(
/Failed to call `useTranslations`, because the context from `NextIntlClientProvider` was not found\./
);
});

it('works with a provider', () => {
render(
<NextIntlClientProvider locale="en" messages={{Component: {test: 'Hello'}}}>
<Component />
</NextIntlClientProvider>
);
screen.getByText('Hello');
});

it('uses error handling for missing messages', () => {
const onError = vi.fn();
render(
<NextIntlClientProvider locale="en" onError={onError}>
<Component />
</NextIntlClientProvider>
);
screen.getByText('Component.test');
expect(onError).toHaveBeenCalled();
});

0 comments on commit 106c098

Please sign in to comment.