Change screen while function runs then change back - javascript

I'm trying to change the screen when a submit button is pressed. The page is essentially just a spinner page. I want it to change to the page while the data is submittied to the database, then change back once the database stuff is finished.
I can get the screen to change but can't get it to change back. Well, I did once, but as soon as I started adding code to the functions it stopped working entirely.
I have a main homepage that has a table component
class Home extends Component {
constructor() {
super()
this.state = {
data: [],
loading: false
};
}
handleSpinner() {
this.setState({ loading: !this.state.loading })
}
handleCancel() {
if (confirm('Are you sure you want to clear the table?')) {
this.setState({ data: [] })
} else {
return
}
}
render() {
return (
this.state.loading ? <WaitingSpinner /> :
<div>
<MainTable data={this.state.data} handleCancel={this.handleCancel} handleSpinner={this.handleSpinner} />
</div>
);
}
The table has a submit button that calls a submit handler.
handleSubmit() {
this.props.handleSpinner()
this.writeToDatabase(this.props.data)
this.props.handleSpinner()
}
async writeToDatabase(data) {
await data.map(test => {
console.log('DATA ', test)
axios.post(`database stuff')
.then(function (response) { console.log(response) }).catch(function (error) { console.log('Err ', error) })
})
}
My goal here is to change to the spinner, run the database function, once that completes it changes back.
At this point it changes to the spinner screen but never changes back to the main table page. I'm guessing it has something to do with when the state actually gets updated but I don't know enough about it to track down what is going on.

Related

Check if data in URL is valid before navigate to page

I would like to configure my Angular component, so that the page only loads if the ID in the URL is valid. The point here is, that I want to protect the page from users manually entering a random URL, and accessing any page.
I have a component with lists.
If I click on the "Show Details", Angular navigates to the details page. I would like to only open this page, if the entered URL contains a valid ID. To achieve this, I call a service to gather all IDs into an array of strings. And then examine if the entered ID is a member of that array.
What I have tried:
list.component.ts:
ngOnInit() {
this.fetchLists();
}
fetchLists() {
from(this.listService.getGroups())
.pipe(
takeUntil(this.destroy$)
)
.subscribe({
next: (listUI: ListUI[]) => {
this.listData = listUI;
},
error: (error) => {
this.logger.debug(error.message);
this.certError = true;
}
});
}
details.component.ts:
ngOnInit() {
this.fetchListsAndIDs();
if (this.validIDsList.includes(listID)) {
this.router.navigateByUrl(`/groups/lists/${listID}/details`);
}
else {this.router.navigateByUrl(`/groups/lists`);}
}
fetchListsAndIDs() {
from(this.listService.getGroups())
.pipe(
takeUntil(this.destroy$)
)
.subscribe({
next: (listUI: ListUI[]) => {
const listData = listUI;
this.validIDsList = listData.map((lists) => lists.id);
},
error: (error) => {
this.logger.debug(error.message);
this.certError = true;
}
});
}
app.routing.module.ts
{
path: 'groups/lists/${listID}/details',
component: DetailsComponent
}
The page "groups/lists/99999999999/details" opens, with zero data, and "this.validIDsList" is undefined. Can someone please help me how to fix this?
You almost have the right code, but you missed the part that, this.fetchListsAndIDs() is executing an asynchronous observable, so your if..else block is executing before even the API call completes.
I would suggest, you include the if...else check inside the next() handler. I have reversed the conditions to check for NOT first, since you are already in details.components.ts which represents ``/groups/lists/${listID}/details) route, you should only redirect the user back to lists if id is not valid, else the component should continue with its work.
I added code to grab the listId from URL. It is missing in the code you posted in the question.
ngOnInit() {
this.listID = this.route.snapshot.paramMap.get('listID');
this.fetchListsAndIDs();
}
fetchListsAndIDs() {
from(this.listService.getGroups())
.pipe(
takeUntil(this.destroy$)
)
.subscribe({
next: (listUI: ListUI[]) => {
const listData = listUI;
this.validIDsList = listData.map((lists) => lists.id);
this.handleNavigation();
},
error: (error) => {
this.logger.debug(error.message);
this.certError = true;
}
});
}
handleNavigation() {
if (!this.validIDsList.includes(this.listID)) {
this.router.navigateByUrl(`/groups/lists`);
} else {
// call the function to continue with details component
}
}

Get updated data from database with reactjs, axios and php

I want to get the latest data from a database and render it to my page.
When i load the page for the first time it works fine, but when i change data in the db nothing changes on the page. Even when i call the update function it does not change anything.
The page im posting to has a php function which retrieves data from the db (mysql).
If im doing something wrong, what would be my best option for this?
class Car extends React.Component {
constructor(props) {
super(props);
this.state = {resultState: "red"};
};
componentDidMount() {
axios.post(`https://web.site/page.php`)
.then(res => {
console.log(res.data);
this.setState({ resultState: res.data });
});
}
update() {
this.forceUpdate();
};
render() {
return (<div>
<div className="row" dangerouslySetInnerHTML={{__html: this.state.resultState}}></div>
</div>)
}
}
ReactDOM.render(<Car />, document.getElementById('mydiv'))
Overall, your code works only if you refresh your page.
A simple solution would be pooling the PHP server. Create a load function whose job is to call the PHP server and in your ComponentDidMount function do the following updates
load() {
axios.post(`https://web.site/page.php`)
.then(res => {
console.log(res.data);
// compare if the data has new values
// if it has then update the state i.e. this.setState({ resultState: res.data });
// otherwise do nothing
});
}
componentDidMount() {
this.load();
// This will call the PHP server every 1 minute
setTimeout(this.load(), 1000);
}
Do not use this.forceUpdate(); as it's not recommended by the react team to be used.

React state variables mysteriously changing before 'beforeunload' function

UPDATE:
I created a minimum reproducible sample here:
https://react-wsaei2.stackblitz.io
Editor link: https://stackblitz.com/edit/react-wsaei2
In my app I'm using a sendBeacon function in beforeunload to unlock a database document when someone closes the page without doing it themselves. Here's how I'm doing that in a useEffect with an empty array so it only runs once on start:
// Add event listener to run code before window closes
window.addEventListener("beforeunload", unlockStory);
return () => {
window.removeEventListener("beforeunload", unlockStory);
}
And here's the unlockStory function:
const unlockStory = (e) => {
e.preventDefault();
if (props.storyID && !success && !loggedOut) {
console.log("Unlocking story. success, loggedOut", success, loggedOut);
debugger;
navigator.sendBeacon(`/api/stories/${props.storyID}/${loggedOut}/${success}`, JSON.stringify({body: {locked: true}}));
}
e.returnValue = "What's going on?";
}
As you see I don't want the beacon to send every time - only if the loggedOut == false (i.e. I don't want to send the Beacon to unlock if the user is already logged out).
The problem is for some reason in the unlockStory function loggedOut is always false, even if it was true right beforehand! I put a manual refresh button on the page to check like so:
const handleRefresh = () => {
console.log("Handling refresh: success, loggedOut", success, loggedOut);
window.location.reload(false);
}
The output is:
Handling refresh: success, loggedOut false true
Unlocking story. success, loggedOut false false
WHYYY????
Another odd thing is that the debugger; line in the unlockStory function doesn't ever get triggered on normal refreshes or page closes, it only gets triggered on refreshes that are causes by me making file changes so npm automatically refreshes the open pages.
Please help, I'm at a loss, thank you!
You need to define success and loggedOut (and other used variables) as effect dependencies.
useEffect(() => {
const unlockStory = (e) => {
e.preventDefault();
console.log("Inside unlockStory. success, loggedOut:", success, loggedOut);
if (!success && !loggedOut) {
console.log("Inside if")
navigator.sendBeacon(`/api/stories/`, JSON.stringify({body: {locked: true}}));
}
e.returnValue = "What's going on?";
}
// Add event listener to run code before window closes
window.addEventListener("beforeunload", unlockStory);
return () => {
window.removeEventListener("beforeunload", unlockStory);
}
}, [success, loggedOut]); // Run on every success or loggedOut change
There is no need to manually remove event listeners as:
React also cleans up effects from the previous render before running the effects next time.
Sometimes is easier to use class based components:
class StartClass extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedOut: false,
success: false,
storyObj: {
segCount: 2
}
}
}
componentDidMount() {
this.setState( { loggedOut: true } );
window.addEventListener("beforeunload", this.unlockStory);
return () => {
window.removeEventListener("beforeunload", this.unlockStory);
}
}
handleRefresh = () => {
let {success, loggedOut} = this.state
console.log("Handling refresh: success, loggedOut", success, loggedOut);
window.location.reload(true);
}
unlockStory = (e) => {
e.preventDefault();
let {success, loggedOut} = this.state
console.log("Inside unlockStory. success, loggedOut:", success, loggedOut);
if (!success && !loggedOut) {
console.log("Inside if")
navigator.sendBeacon(`/api/stories/`, JSON.stringify({body: {locked: true}}));
}
e.returnValue = "What's going on?";
}
render() {
return (
<Container fluid>
<Row>
<Col>
<p>
To reproduce:<br/> Open console, then click the Refresh page button below. When the alert pops up check the console and you'll see that the loggedOut variable has changed from true to false.
</p>
<p>
The same behavior occurs when you refresh via the browser, but you can't see the log as the console is cleared upon refresh.
</p>
{this.state.loggedOut && <Button onClick={this.handleRefresh}>Refresh page</Button>}
</Col>
</Row>
</Container>
);
}
}

react setstate not rendering until callback finishes

I am trying to change button to saving state while I run code to get information.
I have
this.setState({ saving: true }, () => this.save(event) })
In this.save I have a rest call. I can see from the log that the state is updated but visually on the site the button does not go into the spinning circle like it should with that updated value.
Is there a way to force update rendering before running the callback function or a better method to set a button to saving while I do a remote call that could take a little bit of time?
There is no reason to force this. Change your state in parallel to the actual saving:
<button onClick={() => this.save()}>save</button>
paired with:
save() {
this.setState({ saving: true });
remoteAPI.save({
data: this.getSaveData(),
credentials: this.getCredentials()
...
}, response => {
this.setState({ saving: false });
if(response.error) {
// ohnoes!
} else {
// nice.
}
});
}

React: State does not get updated after AJAX call

I'm trying to do two AJAX calls in my React project and have my UI render according to the data received. This is my render method:
render() {
if (this.state.examsLoaded) {
return (
<div>
<Button onClick={this.openModal}>Details</Button>
<Modal show={this.state.modalOpen} onHide={this.closeModal}>
<Modal.Header closeButton>
<Modal.Title>{this.props.course.name}</Modal.Title>
</Modal.Header>
<Modal.Body>
<DetailModalContent course={this.props.course} exams={this.exams} grades={this.grades}/>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.closeModal}>Sluiten</Button>
</Modal.Footer>
</Modal>
</div>
)
}
else {
return (
<div>Loading...</div>
)
}
}
The render method checks if the AJAX data is available yet and if not, just renders a 'Loading...' message. This is the code that fetches the data:
componentDidMount() {
fetch('http://localhost:8080/course/' + this.props.course.id + '/exams').then((examResp) => {
examResp.json().then((examData) => {
this.exams = examData;
console.log('Course data fetched'); // THIS APPEARS
fetch('http://localhost:8080/user/1/grades').then((gradeResponse) => { // THIS DATA IS FETCHED
console.log('Done fetching grades'); // THIS APPEARS
gradeResponse.json((gradeData) => {
console.log('Parsed JSON'); // Here is where it goes wrong. This no longer appears.
this.grades = gradeData;
this.setState({
examsLoaded: true,
modalOpen: false
});
});
});
});
});
},
The weird thing is, I used to only have 1 fetch method and everything would work fine. As soon as I called setState the component rerenders and the data is displayed. However, after adding the second one, it doesn't work anymore. See my console.log's. Everything works fine 'till I parse the JSON, after that, nothing gets run anymore.
What am I doing wrong?
Thanks!
fetch's json() method returns a promise. You are using it correctly in the first call, but the second call you are treating it as a function rather than a promise.
Try
gradeResponse.json().then((gradeData) => {
...
});
You need to write this logic inside componentDidUpdate. componentDidMount will be triggered only for the first time.
Please refer to the React documentation.
Probably you will need both componentDidMount and componentDidUpdate.
componentDidMount() {
fetch('http://localhost:8080/course/' + this.props.course.id + '/exams').then((examResp) => {
examResp.json().then((examData) => {
this.exams = examData;
console.log('Course data fetched'); // THIS APPEARS
this.setState({
examsLoaded: true
}); //At this point some state is changed, so componentDidUpdate will be triggered. Then in that function below, grades will be fetched and state is changed, which should call render again.
});
});
},
componentDidUpdate(){
fetch('http://localhost:8080/user/1/grades').then((gradeResponse) => { // THIS DATA IS FETCHED
console.log('Done fetching grades'); // THIS APPEARS
gradeResponse.json((gradeData) => {
console.log('Parsed JSON'); // Here is where it goes wrong. This no longer appears.
this.grades = gradeData;
this.setState({
examsLoaded: true,
modalOpen: false
});
});
});
}
Since I am not with react environment right now. Will update as soon as I try.

Categories

Resources