Skip to content

Commit

Permalink
Accept an array as localesPath in useLocaleRequired, `withLocaleR…
Browse files Browse the repository at this point in the history
…equired`, and `LocaleRequired` (#779)

* Update useLocalesRequired, withLocaleRequired, and LocaleRequired to accept an array as `localesPath`

* Update Unit Tests

* Update Docs
  • Loading branch information
eliebman-godaddy authored Jun 7, 2024
1 parent 2bc9733 commit b80acff
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 55 deletions.
12 changes: 6 additions & 6 deletions packages/gasket-react-intl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ wrapped component will be rendered.

**Props**

- `localesPath` - (string|function) Path to endpoint with JSON files or
[thunk] that returns one. See more about [locales path] in the plugin docs.
- `localesPath` - (string|function|Array(string|function)) Path to endpoint with JSON files or
[thunk] that returns one. Also supports an array of either. See more about [locales path] in the plugin docs.
- `[options]` - (object) Optional configuration
- `loading` - (string|node) Content to render while loading, otherwise null.
- `initialProps` - (boolean) Enable `getInitialProps` to load locale files
Expand Down Expand Up @@ -99,8 +99,8 @@ content until a [split locales] file loads.

**Props**

- `localesPath` - (string|function) Path to endpoint with JSON files or
[thunk] that returns one. See more about [locales path] in the plugin docs.
- `localesPath` - (string|function|Array(string|function)) Path to endpoint with JSON files or
[thunk] that returns one. Also supports an array of either. See more about [locales path] in the plugin docs.
- `loading` - (string|node) Content to render while loading, otherwise null.

```jsx
Expand Down Expand Up @@ -134,8 +134,8 @@ hook will return the current loading status of the locale file.

**Props**

- `localesPath` - (string|function) Path to endpoint with JSON files or
[thunk] that returns one. See more about [locales path] in the plugin docs.
- `localesPath` - (string|function|Array(string|function)) Path to endpoint with JSON files or
[thunk] that returns one. Also supports an array of either. See more about [locales path] in the plugin docs.

```jsx
import { useLocaleRequired, LocaleStatus } from '@gasket/react-intl';
Expand Down
8 changes: 4 additions & 4 deletions packages/gasket-react-intl/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export type LocaleRequiredWrapper = (props: {
*/
export function withLocaleRequired<Props>(
/** Path containing locale files */
localePathPart?: LocalePathPartOrThunk,
localePathPart?: LocalePathPartOrThunk | LocalePathPartOrThunk[],
options?: {
/** Custom component to show while loading */
loading?: React.ReactNode;
Expand All @@ -50,7 +50,7 @@ export function withLocaleRequired<Props>(

export interface LocaleRequiredProps {
/** Path containing locale files */
localesPath: LocalePathPartOrThunk;
localesPath: LocalePathPartOrThunk | LocalePathPartOrThunk[];
/** Custom component to show while loading */
loading?: React.ReactNode;
}
Expand All @@ -67,7 +67,7 @@ export function LocaleRequired(
*/
export function useLocaleRequired(
/** Path containing locale files */
localePathPart: LocalePathPartOrThunk
localePathPart: LocalePathPartOrThunk | LocalePathPartOrThunk[]
): LocaleStatus;

interface NextStaticContext extends Record<string, any> {
Expand Down Expand Up @@ -148,7 +148,7 @@ export function attachGetInitialProps(
};
},
/** Path containing locale files */
localePathPart: LocalePathPartOrThunk
localePathPart: LocalePathPartOrThunk | LocalePathPartOrThunk[],
): void;

export async function attachedGetInitialProps(
Expand Down
100 changes: 55 additions & 45 deletions packages/gasket-react-intl/src/use-locale-required.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,63 @@ import { GasketIntlContext } from './context';
* React that fetches a locale file and returns loading status
* @type {import('./index').useLocaleRequired}
*/
export default function useLocaleRequired(localePathPart) {
export default function useLocaleRequired(localePathParam) {
const { locale, status = {}, dispatch } = useContext(GasketIntlContext);

// thunks are supported but with context will be browser-only (empty object)
const localePath = localeUtils.getLocalePath(localePathPart, locale);

const fileStatus = status[localePath];
if (fileStatus) return fileStatus;

// We cannot use dispatch from useReducer during SSR, so exit early.
// If you want a locale file to be ready, preload it to gasketIntl data
// or load with getStaticProps or getServerSideProps.
if (!isBrowser) return LocaleStatus.LOADING;

// Mutating status state to avoids an unnecessary render with using dispatch.
status[localePath] = LocaleStatus.LOADING;

const url = localeUtils.pathToUrl(localePath);

// Upon fetching, we will dispatch file status and messages to kick off a render.
fetch(url)
.then((r) =>
r.ok
? r.json()
: Promise.reject(
new Error(`Error loading locale file (${r.status}): ${url}`)
)
)
.then((messages) => {
dispatch({
type: LocaleStatus.LOADED,
payload: {
locale,
messages,
file: localePath
}
});
})
.catch((e) => {
console.error(e.message || e); // eslint-disable-line no-console
dispatch({
type: LocaleStatus.ERROR,
payload: {
file: localePath
}
if (!Array.isArray(localePathParam)) {
localePathParam = [localePathParam];
}

const loadingStatuses = localePathParam.map((localePathPart) => {
// thunks are supported but with context will be browser-only (empty object)
const localePath = localeUtils.getLocalePath(localePathPart, locale);

const fileStatus = status[localePath];
if (fileStatus) return fileStatus;

// We cannot use dispatch from useReducer during SSR, so exit early.
// If you want a locale file to be ready, preload it to gasketIntl data
// or load with getStaticProps or getServerSideProps.
if (!isBrowser) return LocaleStatus.LOADING;

// Mutating status state to avoids an unnecessary render with using dispatch.
status[localePath] = LocaleStatus.LOADING;

const url = localeUtils.pathToUrl(localePath);

// Upon fetching, we will dispatch file status and messages to kick off a render.
fetch(url)
.then((r) =>
r.ok
? r.json()
: Promise.reject(
new Error(`Error loading locale file (${r.status}): ${url}`)
)
)
.then((messages) => {
dispatch({
type: LocaleStatus.LOADED,
payload: {
locale,
messages,
file: localePath
}
});
})
.catch((e) => {
console.error(e.message || e); // eslint-disable-line no-console
dispatch({
type: LocaleStatus.ERROR,
payload: {
file: localePath
}
});
});
});

return LocaleStatus.LOADING;
return LocaleStatus.LOADING;
});

if (loadingStatuses.includes(LocaleStatus.ERROR)) return LocaleStatus.ERROR;
if (loadingStatuses.includes(LocaleStatus.LOADING)) return LocaleStatus.LOADING;
return LocaleStatus.LOADED;
}
50 changes: 50 additions & 0 deletions packages/gasket-react-intl/test/use-locale-required.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const { ERROR, LOADED, LOADING } = LocaleStatus;
// helper to wait for async actions
const pause = ms => new Promise((resolve) => setTimeout(resolve, ms));

// eslint-disable-next-line max-statements
describe('useLocaleRequired', function () {
let mockConfig, mockContext, dispatchMock;

Expand Down Expand Up @@ -115,6 +116,55 @@ describe('useLocaleRequired', function () {
expect(console.error).toHaveBeenCalledWith('Bad things man!');
});

describe('when localesPath is an array', () => {
it('accepts an array of locale paths, and fetches each path provided', () => {
const results = useLocaleRequired(['/locales', '/custom/locales', 'modules/module/locales']);
expect(results).toEqual(LOADING);
expect(fetch).toHaveBeenCalled();
expect(fetch).toHaveBeenCalledWith('/locales/en.json');
expect(fetch).toHaveBeenCalledWith('/custom/locales/en.json');
expect(fetch).toHaveBeenCalledWith('/modules/module/locales/en.json');
});

it('returns ERROR if any of the calls fail', () => {
mockContext.status['/locales/en.json'] = LOADED;
mockContext.status['/custom/locales/en.json'] = ERROR;
mockContext.status['/modules/module/locales/en.json'] = LOADING;

const result = useLocaleRequired(['/locales', '/custom/locales', 'modules/module/locales']);
expect(result).toEqual(ERROR);
});

it('returns LOADING if any of the calls are in progress and none have failed', () => {
mockContext.status['/locales/en.json'] = LOADED;
mockContext.status['/custom/locales/en.json'] = LOADED;
mockContext.status['/modules/module/locales/en.json'] = LOADING;

const result = useLocaleRequired(['/locales', '/custom/locales', 'modules/module/locales']);
expect(result).toEqual(LOADING);
});

it('returns LOADED if all calls succeed', () => {
mockContext.status['/locales/en.json'] = LOADED;
mockContext.status['/custom/locales/en.json'] = LOADED;
mockContext.status['/modules/module/locales/en.json'] = LOADED;

const result = useLocaleRequired(['/locales', '/custom/locales', 'modules/module/locales']);
expect(result).toEqual(LOADED);
});

it('handle array containing thunks', function () {
const mockThunk = jest.fn().mockReturnValue('/custom/locales');

const results = useLocaleRequired(['/locales', mockThunk, 'modules/module/locales']);
expect(results).toEqual(LOADING);
expect(fetch).toHaveBeenCalled();
expect(fetch).toHaveBeenCalledWith('/locales/en.json');
expect(fetch).toHaveBeenCalledWith('/custom/locales/en.json');
expect(fetch).toHaveBeenCalledWith('/modules/module/locales/en.json');
});
});

describe('SSR', function () {

beforeEach(function () {
Expand Down

0 comments on commit b80acff

Please sign in to comment.