Skip to content
This repository was archived by the owner on Aug 3, 2023. It is now read-only.

Commit 5344ecc

Browse files
authored
Merge pull request #152 from jamesplease/fix-bug
Resolve bug with cache mismatch
2 parents 042a79a + f9759f9 commit 5344ecc

File tree

10 files changed

+901
-71
lines changed

10 files changed

+901
-71
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
### v2.0.4 (2018/4/20)
4+
5+
**Bug Fixes**
6+
7+
* Fixes a bug where there could be a cache mismatch when re-rendering the same component
8+
that has a fetch policy configured.
9+
310
### v2.0.3 (2018/3/2)
411

512
**Bug Fixes**

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,9 @@ There are three common use cases for the `doFetch` prop:
225225
is passed as `true`.
226226

227227
`doFetch` accepts one argument: `options`. Any of the `fetch()` options, such as `url`, `method`, and
228-
`body` are valid `options`. This allows you to customize the request from within the component based
229-
on the component's state.
228+
`body` are valid `options`. You may also specify a new `requestKey` if you are manually generating your
229+
own keys. This method allows you to customize the request from within the component based on the
230+
component's state.
230231

231232
##### `lazy`
232233

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-request",
3-
"version": "2.0.3",
3+
"version": "2.0.4",
44
"description": "Declarative HTTP requests with React.",
55
"main": "lib/index.js",
66
"module": "es/index.js",

src/fetch.js

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,22 @@ import { getRequestKey, fetchDedupe, isRequestInFlight } from 'fetch-dedupe';
77
// The value of each key is a Response instance
88
let responseCache = {};
99

10+
// The docs state that this is not safe to use in an
11+
// application. That's just because I am not writing tests,
12+
// nor designing the API, around folks clearing the cache.
13+
// This was only added to help out with testing your app.
14+
// Use your judgment if you decide to use this in your
15+
// app directly.
1016
export function clearResponseCache() {
1117
responseCache = {};
1218
}
1319

1420
export class Fetch extends React.Component {
1521
render() {
16-
const { children, requestName, url } = this.props;
17-
const { fetching, response, data, error, requestKey } = this.state;
22+
// Anything pulled from `this.props` here is not eligible to be
23+
// specified when calling `doFetch`.
24+
const { children, requestName } = this.props;
25+
const { fetching, response, data, error, requestKey, url } = this.state;
1826

1927
if (!children) {
2028
return null;
@@ -49,7 +57,8 @@ export class Fetch extends React.Component {
4957
fetching: false,
5058
response: null,
5159
data: null,
52-
error: null
60+
error: null,
61+
url: props.url
5362
};
5463
}
5564

@@ -95,22 +104,27 @@ export class Fetch extends React.Component {
95104
}
96105
}
97106

98-
componentWillReceiveProps(nextProps) {
107+
// Because we use `componentDidUpdate` to determine if we should fetch
108+
// again, there will be at least one render when you receive your new
109+
// fetch options, such as a new URL, but the fetch has not begun yet.
110+
componentDidUpdate(prevProps) {
99111
const currentRequestKey =
100112
this.props.requestKey ||
101113
getRequestKey({
102114
...this.props,
103115
method: this.props.method.toUpperCase()
104116
});
105-
const nextRequestKey =
106-
nextProps.requestKey ||
117+
const prevRequestKey =
118+
prevProps.requestKey ||
107119
getRequestKey({
108-
...nextProps,
109-
method: this.props.method.toUpperCase()
120+
...prevProps,
121+
method: prevProps.method.toUpperCase()
110122
});
111123

112-
if (currentRequestKey !== nextRequestKey && !this.isLazy(nextProps)) {
113-
this.fetchData(nextProps);
124+
if (currentRequestKey !== prevRequestKey && !this.isLazy(prevProps)) {
125+
this.fetchData({
126+
requestKey: currentRequestKey
127+
});
114128
}
115129
}
116130

@@ -124,6 +138,8 @@ export class Fetch extends React.Component {
124138
cancelExistingRequest = reason => {
125139
if (this.state.fetching && !this.hasHandledNetworkResponse) {
126140
const abortError = new Error(reason);
141+
// This is an effort to mimic the error that is created when a
142+
// fetch is actually aborted using the AbortController API.
127143
abortError.name = 'AbortError';
128144
this.onResponseReceived({
129145
...this.responseReceivedInfo,
@@ -145,11 +161,57 @@ export class Fetch extends React.Component {
145161
});
146162
};
147163

164+
// When a subsequent request is made, it is important that the correct
165+
// request key is used. This method computes the right key based on the
166+
// options and props.
167+
getRequestKey = options => {
168+
// A request key in the options gets top priority
169+
if (options && options.requestKey) {
170+
return options.requestKey;
171+
}
172+
173+
// Otherwise, if we have no request key, but we do have options, then we
174+
// recompute the request key based on these options.
175+
// Note that if the URL, body, or method have not changed, then the request
176+
// key should match the previous request key if it was computed.
177+
// If you passed in a custom request key as a prop, then you will also
178+
// need to pass in a custom key when you call `doFetch()`!
179+
else if (options) {
180+
const { url, method, body } = Object.assign({}, this.props, options);
181+
return getRequestKey({
182+
url,
183+
body,
184+
method: method.toUpperCase()
185+
});
186+
}
187+
188+
// Next in line is the the request key from props.
189+
else if (this.props.requestKey) {
190+
return this.props.requestKey;
191+
}
192+
193+
// Lastly, we compute the request key from the props.
194+
else {
195+
const { url, method, body } = this.props;
196+
197+
return getRequestKey({
198+
url,
199+
body,
200+
method: method.toUpperCase()
201+
});
202+
}
203+
};
204+
148205
fetchData = (options, ignoreCache) => {
206+
// These are the things that we do not allow a user to configure in
207+
// `options` when calling `doFetch()`. Perhaps we should, however.
149208
const { requestName, dedupe, beforeFetch } = this.props;
150209

151210
this.cancelExistingRequest('New fetch initiated');
152211

212+
const requestKey = this.getRequestKey(options);
213+
const requestOptions = Object.assign({}, this.props, options);
214+
153215
const {
154216
url,
155217
body,
@@ -165,16 +227,7 @@ export class Fetch extends React.Component {
165227
integrity,
166228
keepalive,
167229
signal
168-
} = Object.assign({}, this.props, options);
169-
170-
// We need to compute a new key, just in case a new value was passed in `doFetch`.
171-
const requestKey =
172-
this.props.requestKey ||
173-
getRequestKey({
174-
url,
175-
method: method.toUpperCase(),
176-
body
177-
});
230+
} = requestOptions;
178231

179232
const uppercaseMethod = method.toUpperCase();
180233
const shouldCacheResponse = this.shouldCacheResponse();
@@ -205,6 +258,7 @@ export class Fetch extends React.Component {
205258
// If the request config changes, we need to be able to accurately
206259
// cancel the in-flight request.
207260
this.responseReceivedInfo = responseReceivedInfo;
261+
208262
this.hasHandledNetworkResponse = false;
209263

210264
const fetchPolicy = this.getFetchPolicy();
@@ -238,8 +292,11 @@ export class Fetch extends React.Component {
238292
}
239293

240294
this.setState({
241-
fetching: true,
242-
requestKey
295+
requestKey,
296+
url,
297+
error: null,
298+
failed: false,
299+
fetching: true
243300
});
244301
const hittingNetwork = !isRequestInFlight(requestKey) || !dedupe;
245302

@@ -331,10 +388,12 @@ export class Fetch extends React.Component {
331388

332389
this.setState(
333390
{
391+
url,
334392
data,
335393
error,
336394
response,
337-
fetching: stillFetching
395+
fetching: stillFetching,
396+
requestKey
338397
},
339398
() => this.props.onResponse(error, response)
340399
);

test/.eslintrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@
77
"ecmaFeatures": {
88
"jsx": true
99
}
10+
},
11+
"globals": {
12+
"hangingPromise": true
1013
}
1114
}

0 commit comments

Comments
 (0)