Not clearing cache when server responds to form submit with text/vnd.turbo-stream.html
See original GitHub issueSUMMARY
After submitting a non-GET form and server responds with…
- failure => DOES NOT Clear Cache (CORRECT)
- success/redirect response => Clears Cache (CORRECT)
- success/turbo-frame response => DOES NOT Clear Cache (BUG)
- success/turbo-stream response => DOES NOT Clear Cache (BUG)
DETAILS
When submitting a non-GET forms, turbo clears it’s cache:
class FormSubmission {
requestSucceededWithResponse(request, response) {
//...
this.delegate.formSubmissionSucceededWithResponse(this, response);
}
}
}
class Navigator {
async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {
if (formSubmission == this.formSubmission) {
const responseHTML = await fetchResponse.responseHTML;
if (responseHTML) {
if (formSubmission.method != FetchMethod.get) {
this.view.clearSnapshotCache();
}
// ...
}
}
}
}
However, if the server responds with header Content-Type: text/vnd.turbo-stream.html
, then the above logic is skipped and the cache is not cleared.
class StreamObserver {
constructor(delegate) {
//...
this.inspectFetchResponse = event => {
const response = fetchResponseFromEvent(event);
if (response && fetchResponseIsStream(response)) {
event.preventDefault();
this.receiveMessageResponse(response);
}
};
//...
}
}
class FetchRequest {
async receive(response) {
const fetchResponse = new FetchResponse(response);
const event = dispatch("turbo:before-fetch-response", {
cancelable: true,
detail: {
fetchResponse: fetchResponse
},
target: this.target
});
if (event.defaultPrevented) {
this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
} else if (fetchResponse.succeeded) {
this.delegate.requestSucceededWithResponse(this, fetchResponse);
} else {
this.delegate.requestFailedWithResponse(this, fetchResponse);
}
return fetchResponse;
}
}
When StreamObserver
detects fetchResponseIsStream
it calls event.preventDefault()
. Then in FetchRequest#receive
follows the condition event.defaultPrevented
which calls FormSubmission#requestPreventedHandlingResponse
instead of FormSubmission#requestSucceededWithResponse
(mentioned at the beginning of this post). FormSubmission#requestPreventedHandlingResponse
does not clear cache.
Is this intentional? For us it feels like a bug.
Testing a possible solution, the following change clears the cache when the server responds with a turbo-stream:
- requestPreventedHandlingResponse(request, response) {
+ async requestPreventedHandlingResponse(request, response) {
this.result = {
success: response.succeeded,
fetchResponse: response
};
+ const responseHTML = await response.responseHTML;
+ if (responseHTML && this.method != FetchMethod.get) {
+ this.delegate.view.clearSnapshotCache();
+ }
}
We discovered this issue while using data-turbo-permanent
, which cache even with an updated server response.
You can use the following turbo:submit-end
event listener workaround:
const FetchMethod = { get: 0 };
const StreamMessage = { contentType: 'text/vnd.turbo-stream.html' };
document.addEventListener('turbo:submit-end', async ({ detail }) => {
const nonGetFetch = detail.formSubmission.fetchRequest.method !== FetchMethod.get;
const contentType = detail.fetchResponse.response.headers.get('content-type');
const fetchResponseIsStream = contentType.startsWith(StreamMessage.contentType);
const responseHTML = await detail.fetchResponse.responseHTML;
if (detail.success && nonGetFetch && fetchResponseIsStream && responseHTML) {
Turbo.clearCache();
}
});
Issue Analytics
- State:
- Created a year ago
- Comments:8 (8 by maintainers)
Top GitHub Comments
@domchristie - I honestly do not know the purpose of using responseHTML. I added it when mimicking logic used in used to
Navigator#formSubmissionSucceededWithResponse
which checks for the existence ofresponseHTML
in order to clear the cache.see: https://github.com/hotwired/turbo/blob/1ec72ac3236038af50a0010f0821e0664eeac087/src/core/drive/navigator.ts#L86-L105
I think my preference would be to duplicate
view.clearSnapshotCache()
where the response is handled:Navigator#formSubmissionSucceededWithResponse
, already implemented)FrameController#formSubmissionSucceededWithResponse
)Session#receivedMessageFromStream
?—this may require updating a method signature to include the response method)That way
FormSubmission
is just responsible for submitting the form, and not clearing view caches.