Skip to content

Commit 595748b

Browse files
committed
rewrite FetchingProvider with Hooks
1 parent bc132dc commit 595748b

File tree

2 files changed

+108
-104
lines changed

2 files changed

+108
-104
lines changed

src/lib/FetchingProvider.js

Lines changed: 89 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,101 @@
1-
import React, { Component } from 'react';
1+
import React from 'react';
22
import PropTypes from 'prop-types';
3-
43
import { Provider } from './MessageSourceContext';
54

65
const identity = x => x;
76

8-
export class FetchingProvider extends Component {
9-
state = {
10-
translations: {},
11-
fetching: false,
12-
};
13-
14-
mounted = false;
15-
16-
static propTypes = {
17-
/**
18-
* The URL which serves the text messages.
19-
* Required.
20-
*/
21-
url: PropTypes.string.isRequired,
22-
23-
/**
24-
* Makes the rendering of the sub-tree synchronous.
25-
* The components will not render until fetching of the text messages finish.
26-
*
27-
* Defaults to true.
28-
*/
29-
blocking: PropTypes.bool,
30-
31-
/**
32-
* A function which can transform the response received from GET /props.url
33-
* to a suitable format:
34-
*
35-
* Example:
36-
* function transform(response) {
37-
* return response.textMessages;
38-
* }
39-
*/
40-
transform: PropTypes.func,
41-
42-
/**
43-
* Invoked when fetching of text messages starts.
44-
*/
45-
onFetchingStart: PropTypes.func,
46-
47-
/**
48-
* Invoked when fetching of text messages finishes.
49-
*/
50-
onFetchingEnd: PropTypes.func,
51-
52-
/**
53-
* Invoked when fetching fails.
54-
*/
55-
onFetchingError: PropTypes.func,
56-
57-
/**
58-
* Children.
59-
*/
60-
children: PropTypes.node,
61-
};
62-
63-
static defaultProps = {
64-
blocking: true,
65-
transform: identity,
66-
onFetchingStart: identity,
67-
onFetchingEnd: identity,
68-
onFetchingError: identity,
69-
};
70-
71-
componentDidMount() {
72-
this.mounted = true;
73-
this.fetchResources();
74-
}
75-
76-
componentDidUpdate(prevProps) {
77-
if (this.props.url !== prevProps.url) {
78-
this.fetchResources();
79-
}
80-
}
81-
82-
componentWillUnmount() {
83-
this.mounted = false;
84-
}
85-
86-
fetchResources = () => {
87-
const { url, transform, onFetchingStart, onFetchingEnd, onFetchingError } = this.props;
88-
89-
this.setState({ fetching: true }, onFetchingStart);
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+
9026
fetch(url)
9127
.then(r => r.json())
9228
.then(response => {
93-
if (this.mounted) {
94-
this.setState(
95-
{
96-
translations: transform(response),
97-
fetching: false,
98-
},
99-
onFetchingEnd,
100-
);
29+
if (isStillMounted) {
30+
setState({
31+
translations: transform(response),
32+
isFetching: false,
33+
});
34+
onFetchingEnd();
10135
}
10236
})
10337
.catch(onFetchingError);
104-
};
105-
106-
render() {
107-
const { blocking, children } = this.props;
108-
const { translations, fetching } = this.state;
109-
const shouldRenderSubtree = !blocking || (blocking && !fetching);
110-
return <Provider value={translations}>{shouldRenderSubtree ? children : null}</Provider>;
111-
}
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>;
11246
}
47+
48+
FetchingProvider.propTypes = {
49+
/**
50+
* The URL which serves the text messages.
51+
* Required.
52+
*/
53+
url: PropTypes.string.isRequired,
54+
55+
/**
56+
* Makes the rendering of the sub-tree synchronous.
57+
* The components will not render until fetching of the text messages finish.
58+
*
59+
* Defaults to true.
60+
*/
61+
blocking: PropTypes.bool,
62+
63+
/**
64+
* A function which can transform the response received from GET /props.url
65+
* to a suitable format:
66+
*
67+
* Example:
68+
* function transform(response) {
69+
* return response.textMessages;
70+
* }
71+
*/
72+
transform: PropTypes.func,
73+
74+
/**
75+
* Invoked when fetching of text messages starts.
76+
*/
77+
onFetchingStart: PropTypes.func,
78+
79+
/**
80+
* Invoked when fetching of text messages finishes.
81+
*/
82+
onFetchingEnd: PropTypes.func,
83+
84+
/**
85+
* Invoked when fetching fails.
86+
*/
87+
onFetchingError: PropTypes.func,
88+
89+
/**
90+
* Children.
91+
*/
92+
children: PropTypes.node,
93+
};
94+
95+
FetchingProvider.defaultProps = {
96+
blocking: true,
97+
transform: identity,
98+
onFetchingStart: identity,
99+
onFetchingEnd: identity,
100+
onFetchingError: identity,
101+
};

src/lib/FetchingProvider.test.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import React from 'react';
22
import * as RTL from 'react-testing-library';
33
import { FetchingProvider } from './FetchingProvider';
4-
import { withMessages } from './messageSource';
4+
import { useMessageSource } from './useMessageSource';
55

66
describe('FetchingProvider', () => {
7-
const Spy = withMessages(props => props.getMessage('hello.world'));
7+
const Spy = () => {
8+
const { getMessage } = useMessageSource();
9+
return getMessage('hello.world');
10+
};
811

912
beforeEach(() => {
1013
// mock impl of fetch() API
@@ -48,10 +51,17 @@ describe('FetchingProvider', () => {
4851
});
4952

5053
it('fetches text resources when url prop changes', async () => {
54+
const transform = jest.fn(x => x);
55+
const onFetchingStart = jest.fn();
56+
const onFetchingEnd = jest.fn();
5157
function TestComponent(props) {
5258
return (
53-
// eslint-disable-next-line react/prop-types
54-
<FetchingProvider url={props.url}>
59+
<FetchingProvider
60+
url={props.url} // eslint-disable-line react/prop-types
61+
transform={transform}
62+
onFetchingStart={onFetchingStart}
63+
onFetchingEnd={onFetchingEnd}
64+
>
5565
<Spy />
5666
</FetchingProvider>
5767
);
@@ -62,7 +72,12 @@ describe('FetchingProvider', () => {
6272

6373
rerender(<TestComponent url="http://any.uri/texts?lang=de" />);
6474

75+
await RTL.waitForElement(() => getByText('Hello world'));
76+
6577
expect(global.fetch).toHaveBeenCalledTimes(2);
78+
expect(transform).toHaveBeenCalledTimes(2);
79+
expect(onFetchingStart).toHaveBeenCalledTimes(2);
80+
expect(onFetchingEnd).toHaveBeenCalledTimes(2);
6681
});
6782

6883
it('invokes onFetchingError lifecycle on network failure', async () => {

0 commit comments

Comments
 (0)