'Using A fetch() GET Request After A fetch() POST Request Has Been Submitted To Output Database Data Without A Hard Page Refresh
I have a form that submits data with the javascript fetch()
API to MySQL database with PHP.
In the code below, when the form is submitted a success message is outputted on the page and a hard refresh is prevented thanks to the fetch()
API.
The board module itself is initially shown via a click event on an 'Add To Board' element.
Because the boards list is outputted onto the page in a while
loop, I'd like it so the new board name is also outputted in the loop without the page refresh. I thought I could do this by adding a simple GET
request in a separate fetch()
function. This isn't working though (I'm not getting any error messages either).
When a hard refresh of the page happens the new board that is added onto the outputted list and appears on the page as expected, so I know the PHP is working all OK on the backend.
** EDIT **
I've put in the original code I tried, which is basically the same as @willgardner's answer.
Because I'm relatively new to fetch()
and AJAX in general - am I meant to construct (with JavaScript) a new button element in the form that will show the updated result from the get
request? I've assumed the PHP loop would output this onto the page when the get
request happens? Like it does when the page is initially loaded?
I had also missed off the input element in the HTML that is used to post
a board name to the database which is then fetched back with the get
request. This has now been added and is the create-board-name
input element.
JavaScript
// Use fetch() to prevent page doing hard refresh when a new board name is created
let boardModuleForm = document.querySelector('.board-module-form'),
// URL details
myURL = new URL(window.location.href),
pagePath = myURL.pathname
if (boardModuleForm) {
boardModuleForm.addEventListener('submit', function (e) {
if (e.submitter && e.submitter.classList.contains('js-fetch-button')) {
e.preventDefault();
const formData = new FormData(this);
formData.set(e.submitter.name, e.submitter.value);
fetch(pagePath, {
method: 'post',
body: formData
})
.then(function(response) {
if (response.status === 200) {
fetch(pagePath, {
method: 'get',
})
.then(function(response) {
return response.text();
}).catch(function(error) {
console.error(error);
})
}
return response.text();
})
.catch(function(error) {
console.error(error);
})
}
})
}
HTML and some PHP This all works OK because the page returns the correct data when a hard page refresh occurs
<form class="board-module-form" method="post">
<?php
if (isset($_SESSION['logged_in'])) {
$board_stmt = $connection->prepare("SELECT * FROM `boards` WHERE `user_id` = :id ORDER BY id DESC");
$board_stmt -> execute([
':id' => $db_id // variable created when user logs in
]);
while ($board_row = $board_stmt->fetch()) {
$db_board_id = htmlspecialchars($board_row['id']);
$db_board_name = htmlspecialchars($board_row['board_name']);
$db_board_user_id = htmlspecialchars($board_row['user_id']);
?>
<button class="board-list-item" name="board-name" type="submit">
<?php echo $db_board_name; ?>
</button>
<?php
}
}
?>
<div class="submit-wrapper">
<input id="board-name" name="create-board-name" type="text">
<button type="submit" name="submit-board-name" class="js-fetch-button">Submit Board</button>
</div>
</form>
Solution 1:[1]
This looks like an issue with your promises in the JavaScript. I've added some comments below to show where the issue is.
Essentially, the GET fetch request is run before the POST fetch request has finished, so the GET fetch request is not returning the new data that has been POSTed, because it doesn't yet exist in the database.
if (boardModuleForm) {
boardModuleForm.addEventListener('submit', function (e) {
if (e.submitter && e.submitter.classList.contains('js-fetch-button')) {
e.preventDefault();
const formData = new FormData(this);
formData.set(e.submitter.name, e.submitter.value);
/**
* This is asynchronous. To ensure code is run after the (response) promise
* has resolved, it needs to be within the .then() chain.
*/
fetch(pagePath, {
method: 'post',
body: formData
})
.then(function (response) {
if (response.status === 200) {
// output success message
}
return response.text();
})
.catch(function(error) {
console.error(error);
})
// ----- GET REQUEST TO 'FETCH' NEW BOARD NAME FROM DATABASE
/**
* This will run immediately after the fetch method above begins.
* So it will run before the data you POST to the PHP is saved
* to the db, hence when you fetch it, it doesn't return
* the new data.
*/
fetch(pagePath, {
method: 'get',
})
.then(function (response) {
return response.text();
})
.catch(function(error) {
console.error(error);
})
}
})
}
You can solve this by moving the GET fetch request into the chained promises of the POST request:
// Use fetch() to prevent page doing hard refresh when a new board name is created
let boardModuleForm = document.querySelector(".board-module-form"),
// URL details
myURL = new URL(window.location.href),
pagePath = myURL.pathname;
if (boardModuleForm) {
boardModuleForm.addEventListener("submit", function (e) {
if (e.submitter && e.submitter.classList.contains("js-fetch-button")) {
e.preventDefault();
const formData = new FormData(this);
formData.set(e.submitter.name, e.submitter.value);
/**
* This is asynchronous. To ensure code is run after the (response) promise
* has resolved, it needs to be within the .then() chain.
*/
fetch(pagePath, {
method: "post",
body: formData
})
.then(function (response) {
if (response.status === 200) {
// ----- GET REQUEST TO 'FETCH' NEW BOARD NAME FROM DATABASE
/**
* This will now run after the POST request promise has resolved
* and new data successfully added to the db.
*/
fetch(pagePath, {
method: "get"
})
.then(function (response) {
return response.text();
})
.catch(function (error) {
console.error(error);
});
}
return response.text();
})
.catch(function (error) {
console.error(error);
});
}
});
}
If you feel like this is getting a bit messy and you want to avoid callback hell you could switch to using async/await syntax instead of .then() but this is of course entirely optional!
Solution 2:[2]
Requirements
From what I understand, you have,
- A page that lists some boards from the DB.
- POST request adds new data to the DB.
What you are trying is,
- Do a post request that adds the data to DB
- After it's done do a GET request to get new markup
- (You are missing this step) Use the newly obtained markup and replace the page content with the new markup.
Brief
Requests made to a URL with fetch
resolves with the content of that page. What you do with that content is up to you.
In your code you are not doing anything with it.
Idea
So after your get request, you need to get the data and populate it in your document.
fetch( pagePath, {method: "get"} )
.then( function ( response ) {
return response.text();
} )
.then( htmlResponse => {
// Use the data to populate into current document
populateHTMLDataToPage( htmlResponse );
} );
Function populateHTMLDataToPage
But wait, we don't have any populateHTMLDataToPage
function!
That function is the key to the problem. The function is supposed to parse newly received data and put it into the page.
A quick but dirty way would be to replace everything on the page with new content received. This is easy but it's dirty because it will remove all event handlers you have added before.
function populateHTMLDataToPage( htmlResponse ) {
document.open();
document.write( htmlResponse );
document.close();
}
This would mean you'd need to reattach your event handlers to the page because all elements have changed.
What you need is a better implementation of populateHTMLDataToPage
function. Ideally you would want to target only the element with updated content. With class/ID on your list/wrap that contains the loop data. For simplicity let's assume all your loop data is inside <div id='boards'>
and </div>
,
- Parse
htmlResponse
as html, relatively easy with jQuery. - Find your
#boards
element inhtmlResponse
and get its innerHTML. - Find
#boards
in your page and replace it's innerHTML with what's the new one.
So you can test the plausibility of the solution by usnig the provided populateHTMLDataToPage
function. Go on a path where you reattach the event handlers after replacing everything. Or develop your nicer populateHTMLDataToPage
which only updates the part it needs to update.
Solution 3:[3]
PHP code only runs on the server when you load the page initially. When you handle the fetch
GET
request manually in JavaScript on the client-side, the PHP code won't be run at all. So, yes, you need to manually construct the DOM elements in JavaScript, populate them with the information acquired from the server response, and then insert them into the DOM.
It's hard to provide an exact example without knowing the exact shape of your server response and your DOM, but here's how you would read a "name" property out of a json-based server response, and insert it into a new <li>
that's at the end of a <ul id="myList">
element.
fetch(pagePath, { method: "get" }).then(async function (response) {
const { name } = await response.json(); // This will only work if your server returns JSON in the body. If there's another shape to the data, you'll need to experiment/post additional details to your question.
const newListItem = document.createElement("li")
newListItem.innerText = name;
document.getElementById("myList").append(newListItem);
})
Solution 4:[4]
You are sending a POST request and in the .then()
handler you send a GET request. Assuming that the POST is successfully doing its thing on the server and the GET is also correctly getting the response, your issue should be assumed to be client-side only. However, before you assume this, it would not hurt to check (again) that everything works on the server by looking at the Network tab of your developer tools in the browser (right-click anywhere and in the menu that pops up, click on "inspect" or the text that's similar to it).
Please perform the (unsuccessful) test again, perform the operations you have outlined as the repro steps and look at the response of the POST and the GET elements and make sure that it's correct before you proceed to fix the client-side issue. In this answer I'm assuming that the server-side is either correct or you know how to fix it and will focus on the client-side.
This is the relevant part of your code:
fetch(pagePath, {
method: 'post',
body: formData
})
.then(function(response) {
if (response.status === 200) {
fetch(pagePath, {
method: 'get',
})
.then(function(response) {
return response.text(); //incorrect
}).catch(function(error) {
console.error(error);
})
}
return response.text(); //incorrect
})
.catch(function(error) {
console.error(error);
})
I have marked the problematic lines as //incorrect
. You are returning response.text()
instead of applying the response on the UI. Instead of returning it, you will need to refresh your UI with them.
At this point the following were not clarified in the question yet:
- what your response looks alike
- what should the UI do with the response
- what your UI looks alike
So, since these elements are missing from the question, I'm providing you a more abstract answer that nevertheless should help you:
- your POST request receives a
response
, maybe that's also containing important information that you may ignore - you might have an error that you and we are unaware of, it is advisable to check the Console tab of your browser's Dev Tools to see whether your scripts errors out
- your GET request contains information that you do nothing with (you only
return
it in a callbackfunction
) - your HTML needs to be refreshed
So, you will need to carefully study the response of your GET request and write a Javascript code/function that takes the response of the GET as an input and performs the necessary changes on your HTML. If you need help with this, you will need to provide:
- a sample response of the server as an example
- an HTML code
- optionally CSS styling
- description of what should happen to the UI based on the response
Of course, it is strongly advised to provide these in a reproducible and minimal snippet added to your question.
- your UI n
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | willgardner |
Solution 2 | shramee |
Solution 3 | Andrew Stegmaier |
Solution 4 | Lajos Arpad |