React: request to API trigger two times the then block, the request is sended twice - reactjs

I know that the API call must be not placed into render method, but this is just for testing:
I have the below code. When i call the fetchData into the render method the 'Send request ...' message is printed once and the respnse printed twice!?
Output:
Page 1 ... rendering
Send request ...
{data: "Hello there", status: 200, statusText: "", headers: {…}, config: {…}, …}
{data: "Hello there", status: 200, statusText: "", headers: {…}, config: {…}, …}
Why this happens? I have checked also the network tab, and both the request is GET and not a OPTIONS related to CORS.
Also on the server the GET method handler has executed twice.
import React from 'react';
const axios = require('axios').default;
class Page1 extends React.Component {
// componentDidMount() {
// this.fetchData()
// }
fetchData() {
console.log('Send request ...');
axios.get('http://localhost:8080/api/hello/')
.then(function (response) {
console.log(response);
return response.data;
})
.catch(function (error) {
console.log(error);
});
}
render() {
console.log('[Page 1] render called')
this.fetchData();
return (<h1>Hello from Page 1. </h1>);
}
}
export default Page1;

When your application is wrapped in <React.StrictMode> your components will render twice in development environments. This is for error/warning detection. Strict mode will intentionally invoke the following class component functions twice: constructors, the render method, and the shouldComponentUpdate methods. Read more about strict mode in the docs.

Related

React: Can't figure out how to use fetched API response

I'm working with a nice chatbot program for React someone wrote, and the thing is, you can actually bind the bot's responses to function calls like this:
render() {
const { classes } = this.props;
return (
<ChatBot
steps={[
{
...
{
id: '3',
message: ({ previousValue, steps }) => {
this.askAnswer(previousValue)
},
end: true,
},
]}
/>
);
Where message is the answer of the bot that it calculates based on the previousValue and askAnswer is a custom function you'd write. I'm using an API that inputs the previousValue to a GPT model, and I want to print the response of this API.
However, I just can't wrap my head around how I could pass the response of the API to the message. I'm trying this:
constructor(props) {
super(props);
this.state = { response: " " };
}
...
askAnswer(question) {
var jsonData = { "lastConversations": [question] }
fetch('http://my_ip:80/query', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(jsonData)
}).then(response => response.json())
.then(data => { this.setState({ response: data["botResponse"] }) });
return (this.state.response)
}
I've been struggling with this for the past 2-3 hours now.
I've tried a couple of combinations, and nothing seems to work:
If I do as seen above, it seems like this.state.response just won't get
updated (logging data["botResponse"] shows there is a correct
reply, so the API part works and I get the correct response).
If I try async-await for askAnswer and the fetch call, then I can
only return a Promise, which is then incompatible as input for the
ChatBot message.
If I do not await for the fetch to complete, then
this.state.response just stays the default " ".
If I return the correct data["botResponse"] from within the second .then after the fetch, nothing happens.
How am I supposed to get the API result JSON's data["botResponse"] text field out of the fetch scope so that I can pass it to message as a text? Or, how can I get a non-Promise string return after an await (AFAIK this is not possible)?
Thank you!
In your last 2 line
.then(data => { this.setState({ response: data["botResponse"] }) });
return (this.state.response)
You are updating the state and trying to read and return the value in the same function which I think will never work due to async behaviour of state. (this is why your this.state.response in the return statement is the previous value, not the updated state value).
I'm not sure but you can write a callback on this.setState and return from there, the callback will read the updated state value and you can return the new state.

Events firing twice inside axios interceptor

I am trying to implement a interceptor with axios so everytime I receive a response from a request, it checks if it has the field "msg" and if it has, it should show the message to the user.
To do this, I implemented a interceptor in axios, this interceptor should fire an event everytime it receives a response, then the App.js would be listening to this events to show the appropriate message.
My problem is that it seems the event is being fired twice, at first I though it was a problem with PubSub, which is the library that I was using, so I decided to try another framework (eventemitter3) and the exact same thing happened, so I'm totally lost, other people who had similar issues found out that their requests werte actually being fired twice by different components, I'm almost sure that this isn't the problem here, look at the logs when I make a request:
interceptor > Object { data: {…}, status: 200, statusText: "OK", headers: {…}, config: {…}, request: XMLHttpRequest }
intercepted by eventemitter3 > Object { data: null, msg: "Usuário não encontrado", success: true }
intercepted by eventemitter3 > Object { data: null, msg: "Usuário não encontrado", success: true }
intercepted by pubsub > Object { data: null, msg: "Usuário não encontrado", success: true }
intercepted by pubsub > Object { data: null, msg: "Usuário não encontrado", success: true }
notice that there is one "interceptor" log, which means only one request was intercepted, then one event is fired for eventemitter3 and one event is fired for pubsub. The networks tab of the browser only shows one POST and one OPTIONS request. Here is my axiosInstance:
import Axios from 'axios';
import PubSub from 'pubsub-js'
import Emitter from './EventHandler';
export const axios = Axios.create({
baseURL: "http://localhost/api"
})
axios.defaults.headers.common['Authorization'] = localStorage.getItem('jwt') || "";
axios.interceptors.response.use(function (response) {
console.log("interceptor",response)
Emitter.emit('RESPONSE_INTERCEPTED', response.data);
PubSub.publish('RESPONSE_INTERCEPTED', response.data);
return response;
}, function (error) {
return Promise.reject(error);
});
and here is App.js where I listen to the events:
export default function App() {
const alert = useAlert()
Emitter.on('RESPONSE_INTERCEPTED', (data) => {
console.log("intercepted by eventemitter3", data)
alert.show(data.msg)
});
var responseNotifier = function (msg, data) {
if(data.msg){
console.log("intercepted by pubsub", data)
}
};
var token = PubSub.subscribe('RESPONSE_INTERCEPTED', responseNotifier);
return (
<Router>
<div>
<Switch>
<Route path="/sobre"><Sobre /></Route>
<Route path="/memorias"><Memorias /></Route>
<Route path="/login"><Login /></Route>
</Switch>
</div>
</Router>
);
}
just in case it matters, here is the EventHandler for eventemitter3:
import EventEmitter from 'eventemitter3';
const eventEmitter = new EventEmitter();
const Emitter = {
on: (event, fn) => eventEmitter.on(event, fn),
once: (event, fn) => eventEmitter.once(event, fn),
off: (event, fn) => eventEmitter.off(event, fn),
emit: (event, payload) => eventEmitter.emit(event, payload)
}
Object.freeze(Emitter);
export default Emitter;
and the piece of code that makes the request:
login(){
axios.post('/login', {
"username": this.state.username,
"password": this.state.password
}).then((resp)=>{
// console.log(resp)
})
}
I'm really clueless here, everything I found on SO/Google points to requests being fired twice, but the whole application is still in the begining, the code aboce is the only plaec where I fire a request, and the network tab confirms this, I'm fine using whatver event framework or even a completely different solution, I just need to show the message to the user, if someone can point me what I'm doing wrong here It would be of great help for me. Thank you all!
The problem here is not the event firing twice, but being listened twice.
var token = PubSub.subscribe('RESPONSE_INTERCEPTED', responseNotifier);
this piece of code is running twice, probably because the component re-renders (not sure on this though) so everytime an event is fired once, there are two listeners to it doing the exact same thing. You can fix this by using the hook componentDidMount to add your listener. Since you are using a functional component, you can do it like this:
useEffect(()=>{
var token = PubSub.subscribe('RESPONSE_INTERCEPTED', responseNotifier);
})

Axios "uncancel" api calls

I have a pages and inside have a componentDidMount(). We have API calls, however if you navigate away before those calls are resolved you get React error. You can't setState on an unmounted component. So for this reason I've used Axios cancelToken to cancel API calls in componentWillUnmount(). It's working and API calls are being cancelled.
However if you navigate away, and then come back to the same page, I'm seeing that those API calls are still cancelled and not being resolved. Maybe I've implemented cancelToken the wrong way or is there a way to "uncancel" those calls?
Here's codesandbox:
axios cancelToken example
The problem is creating a cancel token that is created in the scope of the file level. So, the first time it's generated and, after that, the request gets cancelled every time without making the request.
const signal = CancelToken.source();
class Roster extends React.Component {
...........
}
So removed the const signal which is declared before the class Roster and included in the constructor of Roster component.
I have modified the code for the Roster Component taken from your sample code, here:
import React from "react";
import axios, { CancelToken } from "axios";
import request from "./api";
class Roster extends React.Component {
constructor() {
super();
this.state = {
data: null,
error: null
};
this.signal = CancelToken.source();
}
componentDidMount() {
request({ url: "google.com", method: "GET", cancelToken: this.signal })
.then(data => this.setState({ data }))
.catch(error => {
if (axios.isCancel(error)) {
this.setState({ error: "request cancelled" });
}
});
}
componentWillUnmount() {
this.signal.cancel();
}
render() {
const { data, error } = this.state;
return (
<div>
<div>data: {data ? data : "no data"}</div>
<div>error: {error ? error : "no error"}</div>
</div>
);
}
}
export default Roster;

react lifting state up after login verified

I'd like to create component - - so that only authorised users can access it. the idea is that after receiving response 200 from backend in my Login component (which is a child component) I would somehow use authed:true as props in parent component. I understand this instructions https://reactjs.org/docs/lifting-state-up.html and also used similar procedure on other components. However, in this case, I am getting the error:
TypeError: this.props.authAfter200 is not a function
at Login.handleSetAuthAfterVerified200 (AuthExample.js:325)
at AuthExample.js:350
at <anonymous>
The problem is I am not able to define function as a prop in my child component.
This is the function in my parent component that should get called as a prop.
setAuthToTrue=() => {
this.setState({authed:true})
}
but I never get to here.
This is how I have defined my Route router in AuthExample(parent) component - using react-router-with-props for that:
<PrivateRoute
exact
path="/private"
redirectTo="/login"
component={DailyNewsForUOnly}
text="This is a private route"
authAfter200={this.setAuthToTrue}
authed={this.state.authed}
/>
this is the part in my child component - Login.js where I should be able to define function on my props to pass to the parent component - but I just cannot:
handleSetAuthAfterVerified200() {
//this.setState({authed:true})
this.props.authAfter200()
}
verifyToken() {
let request = {
method: "post",
url: "https://xxxxxx/xxxx/verify/",
data: {
user: this.state.email,
token: this.state.token
},
withCredentials: true
};
console.log("request to verify");
console.log(request);
this.setState({ requestInMotion: true });
axios(request)
.then(response => {
console.log("response from verify", response);
if (response.status === 200) {
this.handleSetAuthAfterVerified200()
this.setStateAfterVerified200()
}
})
.catch(error => {
console.log(error);
this.setState({ requestInMotion: false, validationState: "error" });
console.log(
"this.state in the error part of response in verify",
this.state
);
});
}
i would suggest u to try to add debuggers in
react-router-with-props/PrivateRoute.js and in
react-router-with-props/renderMergedProps.js
and check if your props are actually being passed on properly.
he is doing very simple job of either allowing react to render your component or to redirect to url provided as prop.
I would suggest you rather use HigherOrderComponents or OnEnter route hook to handle these situations than a third party component.

ReactJs failing to bind data object

I'm new to ReactJs and I'm trying to bind a http response to the DOM, but I can't get it to work. Im using componentDidMount to execute the api call. Any help will be much appreciated, please see code below.
var testStuff="First State";
var Test = React.createClass({
loadTestData: function () {
$.ajax({
url: "http://localhost:64443/api/articles/apitest",
type: 'GET',
cache: false,
dataType: 'json',
success: function (response) {
console.log(response);
testStuff= response;
},
error: function (error) {
alert(error);
}
});
},
componentDidMount() {
this.loadTestData();
},
render() {
console.log(this.testStuff);
return (
<div onClick={this.loadTestData}>
{testStuff}
</div>
);
}
});
ReactDOM.render(
<Test />,
document.getElementById('content')
);
The onClick seems to be working, but its almost like the componentDidMount only gets executed after the page is rendered.
You need to properly update the component state in the AJAX success handler: this.setState({ testStuff: data })
Then you will be able to access it in render using this.state.testStuff.
componentDidMount indeed only runs after component has been mounted. You will also need to represent the UI state where the requests is still ongoing (this.state.testStuff will be undefined at that point).

Resources