Skip to content

Commit 5b57376

Browse files
committed
Rewrite with Typescript
1 parent 46e22b4 commit 5b57376

23 files changed

+454
-349
lines changed

.babelrc.js

Lines changed: 0 additions & 16 deletions
This file was deleted.

.eslintignore

Lines changed: 0 additions & 3 deletions
This file was deleted.

.eslintrc

Lines changed: 0 additions & 26 deletions
This file was deleted.

package.json

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
"author": "Netcetera AG",
1313
"license": "MIT",
1414
"repository": "https://github.com/netceteragroup/react-message-source",
15-
"main": "dist/index.js",
16-
"module": "dist/index.es.js",
17-
"jsnext:main": "dist/index.es.js",
15+
"main": "dist/react-message-source.js",
16+
"module": "dist/react-message-source.es.js",
17+
"jsnext:main": "dist/react-message-source.es.js",
1818
"engines": {
1919
"node": ">=8",
2020
"npm": ">=5"
@@ -23,6 +23,7 @@
2323
"test": "cross-env CI=1 react-scripts test --env=jsdom",
2424
"test:watch": "react-scripts test --env=jsdom",
2525
"coverage": "cross-env CI=1 react-scripts test --coverage --coverageReporters=text-lcov | coveralls",
26+
"prebuild": "rimraf dist",
2627
"build": "rollup -c",
2728
"start": "rollup -c -w",
2829
"prepare": "yarn run build",
@@ -37,31 +38,34 @@
3738
"react": "^16.8.0"
3839
},
3940
"devDependencies": {
40-
"@babel/core": "^7.3.4",
41-
"@babel/plugin-proposal-class-properties": "^7.3.4",
42-
"@babel/preset-env": "^7.3.4",
43-
"@babel/preset-react": "^7.0.0",
41+
"@types/hoist-non-react-statics": "^3.3.0",
42+
"@types/invariant": "^2.2.29",
43+
"@types/jest": "^24.0.11",
44+
"@types/react": "^16.8.8",
45+
"@types/react-dom": "^16.8.2",
46+
"@types/react-test-renderer": "^16.8.1",
4447
"coveralls": "^3.0.3",
4548
"cross-env": "^5.2.0",
46-
"eslint-config-airbnb": "^17.1.0",
47-
"eslint-config-prettier": "^4.1.0",
48-
"eslint-plugin-import": "^2.16.0",
49-
"eslint-plugin-jsx-a11y": "^6.2.1",
50-
"eslint-plugin-prettier": "^3.0.1",
51-
"eslint-plugin-react": "^7.12.4",
52-
"eslint-plugin-react-hooks": "^1.5.1",
5349
"prettier": "^1.16.4",
5450
"prop-types": "^15.7.2",
5551
"react": "^16.8.4",
5652
"react-dom": "^16.8.4",
5753
"react-scripts": "2.1.8",
5854
"react-test-renderer": "^16.8.4",
5955
"react-testing-library": "^6.0.0",
56+
"rimraf": "^2.6.3",
6057
"rollup": "^1.6.0",
61-
"rollup-plugin-babel": "^4.3.2",
6258
"rollup-plugin-commonjs": "^9.2.1",
6359
"rollup-plugin-node-resolve": "^4.0.1",
64-
"rollup-plugin-peer-deps-external": "^2.2.0"
60+
"rollup-plugin-peer-deps-external": "^2.2.0",
61+
"rollup-plugin-typescript2": "^0.20.1",
62+
"tslib": "^1.9.3",
63+
"tslint": "^5.14.0",
64+
"tslint-config-prettier": "^1.18.0",
65+
"tslint-plugin-prettier": "^2.0.1",
66+
"tslint-react": "^3.6.0",
67+
"tslint-react-hooks": "^2.0.0",
68+
"typescript": "^3.3.3333"
6569
},
6670
"files": [
6771
"dist"

rollup.config.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
1-
import babel from 'rollup-plugin-babel';
1+
import typescript from 'rollup-plugin-typescript2';
22
import commonjs from 'rollup-plugin-commonjs';
33
import external from 'rollup-plugin-peer-deps-external';
44
import resolve from 'rollup-plugin-node-resolve';
55

66
import pkg from './package.json';
77

88
module.exports = {
9-
input: 'src/index.js',
9+
input: 'src/index.ts',
1010
output: [
1111
{
1212
file: pkg.main,
1313
format: 'cjs',
14-
sourcemap: true,
14+
exports: 'named',
15+
sourcemap: true
1516
},
1617
{
1718
file: pkg.module,
1819
format: 'es',
19-
sourcemap: true,
20-
},
20+
exports: 'named',
21+
sourcemap: true
22+
}
2123
],
2224
plugins: [
2325
external(),
24-
babel(),
2526
resolve(),
27+
typescript({
28+
rollupCommonJSResolveHack: true,
29+
clean: true,
30+
}),
2631
commonjs()
27-
].filter(Boolean),
32+
],
2833
};
File renamed without changes.
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import React from 'react';
1+
import * as React from 'react';
22
import * as RTL from 'react-testing-library';
33
import { FetchingProvider } from './FetchingProvider';
44
import { useMessageSource } from './useMessageSource';
55

66
describe('FetchingProvider', () => {
7-
const Spy = () => {
7+
function Spy() {
88
const { getMessage } = useMessageSource();
9-
return getMessage('hello.world');
10-
};
9+
return <span>{getMessage('hello.world')}</span>;
10+
}
1111

1212
beforeEach(() => {
1313
// mock impl of fetch() API
14+
// @ts-ignore
1415
global.fetch = jest.fn(() =>
1516
Promise.resolve({
1617
json: () =>
@@ -47,14 +48,15 @@ describe('FetchingProvider', () => {
4748
expect(transform).toHaveBeenCalled();
4849
expect(onFetchingStart).toHaveBeenCalled();
4950
expect(onFetchingEnd).toHaveBeenCalled();
51+
// @ts-ignore
5052
expect(global.fetch).toHaveBeenCalledTimes(1);
5153
});
5254

5355
it('fetches text resources when url prop changes', async () => {
5456
const transform = jest.fn(x => x);
5557
const onFetchingStart = jest.fn();
5658
const onFetchingEnd = jest.fn();
57-
function TestComponent(props) {
59+
function TestComponent(props: { url: string }) {
5860
return (
5961
<FetchingProvider
6062
url={props.url} // eslint-disable-line react/prop-types
@@ -82,6 +84,7 @@ describe('FetchingProvider', () => {
8284
}),
8385
);
8486

87+
// @ts-ignore
8588
expect(global.fetch).toHaveBeenCalledTimes(2);
8689
expect(transform).toHaveBeenCalledTimes(2);
8790
expect(onFetchingStart).toHaveBeenCalledTimes(2);
@@ -91,9 +94,10 @@ describe('FetchingProvider', () => {
9194
it('invokes onFetchingError lifecycle on network failure', async () => {
9295
const onFetchingError = jest.fn();
9396
const faultyFetch = jest.fn(() => Promise.reject(new Error('Failure')));
97+
// @ts-ignore
9498
global.fetch = faultyFetch;
9599

96-
RTL.render(<FetchingProvider url="http://any.uri/texts" onFetchingError={onFetchingError} />);
100+
RTL.render(<FetchingProvider url="http://any.uri/texts" onFetchingError={onFetchingError} children={null} />);
97101
await RTL.wait(); // until fetch() rejects
98102

99103
expect(faultyFetch).toHaveBeenCalledTimes(1);
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,20 @@
1-
import React from 'react';
2-
import PropTypes from 'prop-types';
3-
import { Provider } from './MessageSourceContext';
1+
import * as React from 'react';
2+
import { MessageSourceContextShape, Provider } from './MessageSourceContext';
43

5-
const identity = x => x;
4+
const identity = (x: any): any => x;
5+
const noop = () => {};
66

7-
const initialState = {
8-
translations: {},
9-
isFetching: false,
10-
};
11-
12-
/**
13-
* A special <Provider /> which can load translations from remote URL
14-
* via a `GET` request and pass them down the component tree.
15-
*/
16-
export function FetchingProvider(props) {
17-
const { url, blocking, children, transform, onFetchingStart, onFetchingEnd, onFetchingError } = props;
18-
const [{ translations, isFetching }, setState] = React.useState(initialState);
19-
20-
React.useEffect(() => {
21-
let isStillMounted = true;
22-
23-
setState(state => ({ ...state, isFetching: true }));
24-
onFetchingStart();
25-
26-
fetch(url)
27-
.then(r => r.json())
28-
.then(response => {
29-
if (isStillMounted) {
30-
setState({
31-
translations: transform(response),
32-
isFetching: false,
33-
});
34-
onFetchingEnd();
35-
}
36-
})
37-
.catch(onFetchingError);
38-
39-
return () => {
40-
isStillMounted = false;
41-
};
42-
}, [url]); // re-fetch only when url changes
43-
44-
const shouldRenderSubtree = !blocking || (blocking && !isFetching);
45-
return <Provider value={translations}>{shouldRenderSubtree ? children : null}</Provider>;
46-
}
47-
48-
FetchingProvider.propTypes = {
7+
export interface FetchingProviderApi {
498
/**
509
* The URL which serves the text messages.
51-
* Required.
5210
*/
53-
url: PropTypes.string.isRequired,
11+
url: string;
5412

5513
/**
5614
* Makes the rendering of the sub-tree synchronous.
5715
* The components will not render until fetching of the text messages finish.
58-
*
59-
* Defaults to true.
6016
*/
61-
blocking: PropTypes.bool,
17+
blocking?: boolean;
6218

6319
/**
6420
* A function which can transform the response received from GET /props.url
@@ -69,33 +25,80 @@ FetchingProvider.propTypes = {
6925
* return response.textMessages;
7026
* }
7127
*/
72-
transform: PropTypes.func,
28+
transform?: (x: any) => MessageSourceContextShape;
7329

7430
/**
7531
* Invoked when fetching of text messages starts.
7632
*/
77-
onFetchingStart: PropTypes.func,
33+
onFetchingStart?: () => void;
7834

7935
/**
8036
* Invoked when fetching of text messages finishes.
8137
*/
82-
onFetchingEnd: PropTypes.func,
38+
onFetchingEnd?: () => void;
8339

8440
/**
8541
* Invoked when fetching fails.
8642
*/
87-
onFetchingError: PropTypes.func,
43+
onFetchingError?: (e: Error) => void;
8844

8945
/**
9046
* Children.
9147
*/
92-
children: PropTypes.node,
48+
children: React.ReactNode;
49+
}
50+
51+
type State = {
52+
translations: MessageSourceContextShape,
53+
isFetching: boolean,
9354
};
9455

95-
FetchingProvider.defaultProps = {
96-
blocking: true,
97-
transform: identity,
98-
onFetchingStart: identity,
99-
onFetchingEnd: identity,
100-
onFetchingError: identity,
56+
const initialState: State = {
57+
translations: {},
58+
isFetching: false,
10159
};
60+
61+
/**
62+
* A special <Provider /> which can load translations from remote URL
63+
* via a `GET` request and pass them down the component tree.
64+
*/
65+
export function FetchingProvider(props: FetchingProviderApi) {
66+
const {
67+
url,
68+
children,
69+
blocking = true,
70+
transform = identity,
71+
onFetchingStart = noop,
72+
onFetchingEnd = noop,
73+
onFetchingError = noop,
74+
} = props;
75+
76+
const [{ translations, isFetching }, setState] = React.useState<State>(initialState);
77+
78+
React.useEffect(() => {
79+
let isStillMounted = true;
80+
81+
setState(state => ({ ...state, isFetching: true }));
82+
onFetchingStart();
83+
84+
fetch(url)
85+
.then(r => r.json())
86+
.then(response => {
87+
if (isStillMounted) {
88+
setState({
89+
translations: transform(response),
90+
isFetching: false,
91+
});
92+
onFetchingEnd();
93+
}
94+
})
95+
.catch(onFetchingError);
96+
97+
return () => {
98+
isStillMounted = false;
99+
};
100+
}, [url]); // re-fetch only when url changes
101+
102+
const shouldRenderSubtree = !blocking || (blocking && !isFetching);
103+
return <Provider value={translations}>{shouldRenderSubtree ? children : null}</Provider>;
104+
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import React from 'react';
1+
import * as React from 'react';
2+
3+
export type MessageSourceContextShape = {
4+
[key: string]: string,
5+
};
26

37
/**
48
* Initial Context value, an empty object.
@@ -8,7 +12,7 @@ const empty = {};
812
/**
913
* A React Context which holds the translations map.
1014
*/
11-
const MessageSourceContext = React.createContext(empty);
15+
const MessageSourceContext = React.createContext<MessageSourceContextShape>(empty);
1216
MessageSourceContext.displayName = 'MessageSourceContext';
1317

1418
/**

0 commit comments

Comments
 (0)