Skip to content

Commit 12778a0

Browse files
committed
Use new React.createContext to expose state in deep trees. Cleanup and improve children handling after requiring React 16.3. Remove chaining children functions (middleware) - better served via context and other hooks (fetchFunction).
1 parent e069756 commit 12778a0

File tree

6 files changed

+43
-124
lines changed

6 files changed

+43
-124
lines changed

package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
"babel-preset-env": "^1.6.1",
1818
"fetch-mock": "^6.3.0",
1919
"jest": "^22.4.3",
20-
"react": "^16.2.0",
21-
"react-dom": "^16.2.0",
22-
"react-test-renderer": "^16.2.0",
20+
"react": "^16.3.2",
21+
"react-dom": "^16.3.2",
22+
"react-test-renderer": "^16.3.2",
2323
"react-testing-library": "^2.3.0"
2424
},
2525
"peerDependencies": {
26-
"react": ">=15.4.2"
26+
"react": "^16.3.0"
2727
},
2828
"scripts": {
2929
"test": "jest",

src/Fetch.js

+20-3
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ import React, { Component } from 'react';
33
import { parseBody, renderChildren } from './utils';
44
import SimpleCache from './SimpleCache';
55

6+
const FetchContext = React.createContext({});
7+
68
export default class Fetch extends Component {
79
static defaultProps = {
810
as: 'auto',
911
fetchFunction: (url, options) => fetch(url, options)
1012
};
1113

14+
static Consumer = FetchContext.Consumer;
15+
1216
state = {
1317
request: {
1418
url: this.props.url,
@@ -29,7 +33,9 @@ export default class Fetch extends Component {
2933
this.cache =
3034
this.props.cache === true
3135
? new SimpleCache()
32-
: typeof this.props.cache === 'object' ? this.props.cache : null;
36+
: typeof this.props.cache === 'object'
37+
? this.props.cache
38+
: null;
3339
}
3440

3541
componentDidMount() {
@@ -90,7 +96,9 @@ export default class Fetch extends Component {
9096
? as(response)
9197
: typeof as === 'object'
9298
? parseBody(response, as)
93-
: as === 'auto' ? parseBody(response) : response[as]();
99+
: as === 'auto'
100+
? parseBody(response)
101+
: response[as]();
94102

95103
return dataPromise
96104
.then(data => ({ response, data }))
@@ -186,6 +194,15 @@ export default class Fetch extends Component {
186194

187195
render() {
188196
const { children } = this.props;
189-
return renderChildren(children, this.state);
197+
198+
return (
199+
<FetchContext.Provider value={this.state}>
200+
{typeof children === 'function' ? (
201+
<FetchContext.Consumer>{children}</FetchContext.Consumer>
202+
) : (
203+
children
204+
)}
205+
</FetchContext.Provider>
206+
);
190207
}
191208
}

src/Fetch.test.js

+4-89
Original file line numberDiff line numberDiff line change
@@ -2172,103 +2172,18 @@ describe('onDataChange', () => {
21722172
});
21732173
});
21742174

2175-
describe('middleware', () => {
2176-
it('supports interceptor / middlware', async () => {
2177-
const url = 'http://localhost';
2178-
const data = { hello: 'world' };
2179-
fetchMock.once(url, data);
2180-
2181-
// const mockInterceptor = jest.fn();
2182-
// mockChildren.mockReturnValue(<div />)
2183-
2184-
const mockChildren = jest.fn();
2185-
mockChildren.mockReturnValue(<div />);
2186-
2187-
const middleware = fetchProps => mockChildren;
2188-
2189-
const {} = render(<Fetch url={url}>{middleware}</Fetch>);
2190-
2191-
// Once for initial, once for loading, and once for response
2192-
await wait(() => expect(mockChildren.mock.calls.length).toBe(3));
2193-
2194-
// Initial state
2195-
expect(mockChildren.mock.calls[0][0]).toMatchObject({
2196-
loading: null,
2197-
request: {}
2198-
});
2199-
2200-
// Loading...
2201-
expect(mockChildren.mock.calls[1][0]).toMatchObject({
2202-
loading: true,
2203-
request: {}
2204-
});
2205-
2206-
// Data loaded
2207-
expect(mockChildren.mock.calls[2][0]).toMatchObject({
2208-
loading: false,
2209-
data,
2210-
request: {},
2211-
response: {}
2212-
});
2213-
2214-
expect(fetchMock.called(url)).toBe(true);
2215-
});
2216-
});
2217-
2218-
describe('middleware', () => {
2219-
it('supports interceptor / middlware', async () => {
2220-
const url = 'http://localhost';
2221-
const data = { hello: 'world' };
2222-
fetchMock.once(url, data);
2223-
2224-
// const mockInterceptor = jest.fn();
2225-
// mockChildren.mockReturnValue(<div />)
2226-
2227-
const mockChildren = jest.fn();
2228-
mockChildren.mockReturnValue(<div />);
2229-
2230-
const middleware = fetchProps => mockChildren;
2231-
2232-
const {} = render(<Fetch url={url}>{middleware}</Fetch>);
2233-
2234-
// Once for initial, once for loading, and once for response
2235-
await wait(() => expect(mockChildren.mock.calls.length).toBe(3));
2236-
2237-
// Initial state
2238-
expect(mockChildren.mock.calls[0][0]).toMatchObject({
2239-
loading: null,
2240-
request: {}
2241-
});
2242-
2243-
// Loading...
2244-
expect(mockChildren.mock.calls[1][0]).toMatchObject({
2245-
loading: true,
2246-
request: {}
2247-
});
2248-
2249-
// Data loaded
2250-
expect(mockChildren.mock.calls[2][0]).toMatchObject({
2251-
loading: false,
2252-
data,
2253-
request: {},
2254-
response: {}
2255-
});
2256-
2257-
expect(fetchMock.called(url)).toBe(true);
2258-
});
2259-
// TODO: Add test for conditional returning based on request.status (ex. 401 returns login, 200 returns `children`)
2260-
2261-
it('supports multiple interceptors / middlwares', async () => {
2175+
describe('context', () => {
2176+
it('deeply nested consumer', async () => {
22622177
const url = 'http://localhost';
22632178
const data = { hello: 'world' };
22642179
fetchMock.once(url, data);
22652180

22662181
const mockChildren = jest.fn();
22672182
mockChildren.mockReturnValue(<div />);
22682183

2269-
const middleware = fetchProps => fetchProps => mockChildren;
2184+
const Listener = () => <Fetch.Consumer>{mockChildren}</Fetch.Consumer>
22702185

2271-
const {} = render(<Fetch url={url}>{middleware}</Fetch>);
2186+
const {} = render(<Fetch url={url}><div><div><div><Listener /></div></div></div></Fetch>);
22722187

22732188
// Once for initial, once for loading, and once for response
22742189
await wait(() => expect(mockChildren.mock.calls.length).toBe(3));

src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export { default } from './Fetch';
22
export { default as SimpleCache } from './SimpleCache';
3-
export { renderChildren, parseBody } from './utils';
3+
export { parseBody } from './utils';

src/utils.js

-18
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,5 @@
11
import React from 'react';
22

3-
export function renderChildren(children, fetchProps) {
4-
if (typeof children === 'function') {
5-
const childrenResult = children(fetchProps);
6-
if (typeof childrenResult === 'function') {
7-
return renderChildren(childrenResult, fetchProps);
8-
} else {
9-
return childrenResult;
10-
}
11-
} else if (React.Children.count(children) === 0) {
12-
return null;
13-
} else {
14-
// DOM/Component children
15-
// TODO: Better to check if children count === 1 and return null otherwise (like react-router)?
16-
// Currently not possible to support multiple children components/elements (until React fiber)
17-
return React.Children.only(children);
18-
}
19-
}
20-
213
export const parseBody = (response, mapping = {}) => {
224
const contentType = response.headers.get('Content-Type');
235

yarn.lock

+14-9
Original file line numberDiff line numberDiff line change
@@ -3411,22 +3411,27 @@ rc@~1.1.6:
34113411
minimist "^1.2.0"
34123412
strip-json-comments "~1.0.4"
34133413

3414-
react-dom@^16.2.0:
3415-
version "16.2.0"
3416-
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044"
3414+
react-dom@^16.3.2:
3415+
version "16.3.2"
3416+
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df"
34173417
dependencies:
34183418
fbjs "^0.8.16"
34193419
loose-envify "^1.1.0"
34203420
object-assign "^4.1.1"
34213421
prop-types "^15.6.0"
34223422

3423-
react-test-renderer@^16.2.0:
3424-
version "16.2.0"
3425-
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.2.0.tgz#bddf259a6b8fcd8555f012afc8eacc238872a211"
3423+
react-is@^16.3.2:
3424+
version "16.3.2"
3425+
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22"
3426+
3427+
react-test-renderer@^16.3.2:
3428+
version "16.3.2"
3429+
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.3.2.tgz#3d1ed74fda8db42521fdf03328e933312214749a"
34263430
dependencies:
34273431
fbjs "^0.8.16"
34283432
object-assign "^4.1.1"
34293433
prop-types "^15.6.0"
3434+
react-is "^16.3.2"
34303435

34313436
react-testing-library@^2.3.0:
34323437
version "2.3.0"
@@ -3435,9 +3440,9 @@ react-testing-library@^2.3.0:
34353440
dom-testing-library "^1.10.0"
34363441
wait-for-expect "^0.5.0"
34373442

3438-
react@^16.2.0:
3439-
version "16.2.0"
3440-
resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
3443+
react@^16.3.2:
3444+
version "16.3.2"
3445+
resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9"
34413446
dependencies:
34423447
fbjs "^0.8.16"
34433448
loose-envify "^1.1.0"

0 commit comments

Comments
 (0)