Events firing twice inside axios interceptor - reactjs

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);
})

Related

FullCalendar events (as a json feed) is getting re-fetched after eventClick and dayClick

I am using FullCalendar in a React page and in order to get the events I am using events as a json feed (https://fullcalendar.io/docs/events-json-feed). The data gets loaded fine however when I use eventClick or dateClick to open a modal, FullCalendar is refreshing and another POST request is sent to the backend.
Is there a way to prevent that? I want to avoid sending unnecessary requests...
Also, as the data gets refreshed the calendar events are re-drawn and this causes to look like a glitch. Similar to this:
https://user-images.githubusercontent.com/3365507/85287154-6fc61c00-b4c6-11ea-83c1-cb72a3aec944.gif
Here are a few examples of the code I am using:
<FullCalendar
...
eventClick={handleEventClick}
dateClick={handleDateClick}
eventSources={[
{
events: fetchEvents,
failure: function() {
console.log('ERROR');
}
},
]}
...
/>
And fetchEvents is something like this:
const fetchEvents = (fetchInfo, successCallback, failureCallback) => {
fetch('http://localhost/calendarEvents', {
method: 'POST',
body: JSON.stringify(fetchInfo),
headers: {
'Content-Type': 'application/json',
},
})
.then((res) => res.json())
.then((data) => {
const parsedEvents = [];
for (const event of data) {
parsedEvents.push({
...event,
start: moment(event.startAt).toDate(),
end: moment(event.endAt).toDate(),
title: event.title
});
}
successCallback(parsedEvents);
})
.catch((error) => {
failureCallback(error);
});
}
and handleEventClick:
const handleEventClick = (event) => {
setSelectedEvent(event);
setOpenEventModal(true);
};
--EDIT--
Here is a CodeSandbox example:
https://codesandbox.io/s/suspicious-murdock-4jfept?file=/src/App.js
You can see at the Console tab that a new fetch is tried each time you click at a date to open the Modal. A new fetch is expected only when switching months in the calendar because I am using eventSources json feed option. But if it was already fetched it shouldn't do it again just by opening the Modal.
setSelectedEvent(event);
setOpenEventModal(true);
If state changes in <FullCalendar> it will rerender. This may be causing it to call for the data again.
Either stop changing the state in FullCalendar, do your API calls outside and pass in the data, or don't call for the data on every render.
What is the full code for <FullCalendar>?

How can I optimize my code to stop sending GET requests constantly?

I am using the Yelp Fusion API to get a list of restaurants from Yelp. However, I am always constantly sending a GET request and I am not sure what is going on or how to fix it. I have tried React.memo and useCallback. I think the problem lies within how I am making the call rather than my component rerendering.
Here is where I send a GET request
// Function for accessing Yelp Fusion API
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
// Saving our results, getting first 5 restaurants,
// and turning off our loading screen
setYelpResults({businesses: response.data.businesses.splice(0, 5)});
setEnableLoading(1);
}
catch (error) {
setEnableLoading(2);
}
};
This is where I use axios.
// Our Yelp Fusion code that sends a GET request
export default axios.create({
baseURL: `${'https://cors-anywhere.herokuapp.com/'}https://api.yelp.com/v3`,
headers: {
Authorization: `Bearer ${KEY}`
},
})
You are probably calling that function within your functional component and that function sets a state of that component, so it re-renders. Then the function is executed again, sets state, re-renders and so on...
What you need to do is to wrap that API call inside a:
useEffect(() => {}, [])
Since you probably want to call it one time. See useEffect doc here
You can do 2 things either use a button to get the list of restaurants because you are firing your function again and again.
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
Use a button instead maybe so once that button is clicked function is fired.
<button onClick={yelpFusionSearch} />Load More Restaurants </button>
Use your fuction inside useEffect method which will load 5 restaurants once the page renders
useEffect(() => {
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
}, [])

How to handle the case where the axios response comes after the component is rendered?

I am creating a blog application in rest framework and reactjs. On the home page, under componentDidMount, I send an API call using axios to get all the articles and setState of articles to the return. As I have studied, axios works on the idea of promise such that the code doesnt proceed, if the API is not fetched for a particular component. Please tell me, if I am wrong.
Then, I send a GET call to get the writer's name, who wrote the article by the id. Though, I assumed that the axios works as a promise. But, it doesnt work that way. Now, I am not sure how to move ahead.
Here is a snippet. So, in mainBody.js, I make the api call as:
class MainBody extends Component {
state = {};
componentDidMount () {
this.get_all_articles();
};
get_writer_name (id) {
let authstr = 'Bearer ' + window.localStorage.token;
let writer_url = "http://localhost:8000/api/writer/" + id.toString() + "/";
axios.get(writer_url, { headers: { Authorization: authstr }})
.then(response => {
console.log(response.data['name'])
return response.data['name'];
})
.catch(err => {
console.log("Got error")
})
};
get_all_articles () {
let authstr = 'Bearer ' + window.localStorage.token;
axios.get("http://localhost:8000/api/articles/", { headers: { Authorization: authstr }})
.then(response => {
this.setState({articles: response.data});
})
.catch(err => {
console.log("Got error")
})
}
render () {
return (
{this.state.articles.map((article, key) =>
<ArticleView key={article.id} article={article} writer_name={this.get_writer_name(article.created_by)} />
)}
)
}
}
In articleview2, I print all the data that is present in each of the articles along with the writer's name.
My articleview class is:
class ArticleView extends Component {
state = {article: this.props.article};
componentDidMount() {
console.log(this.props.writer_name;
}
render () {
return (
<React.Fragment>
<h2>{article.title}</h2>
<p>{article.body}</p>
<span>{this.props.writer_name}</span>
</React.Fragment>
)
}
}
If you see closely, I wrote two console.log statements to get the writer names. Based on the order, first the console log present in articleview class runs, which is undefined, and thenafter the data is fetched from the API call and the console log runs which returns the correct writer name.
I wanted to know, where is the error? Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
I want to know where is the error.
When you are writing this.state.articles.map(), means you're using property map of the Array articles which may be undefined before the data is fetched that will cause you the error Cannot read property map of undefined.
Solution
Now, as the API request is asynchronous, means render method will not wait for the data to come. So what you can do is use a loader variable in the state, and set it to true as long as the request is being made, and when the response has come, make it false, and show the loader in render when this.state.loader is true, and show articles when it is false.
Or you can initialize this.state.articles with an empty array that won't cause you the error.
Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
It is extremely bad practice to make an API request in the loop. Even myself has been scolded on it once I did it in my company.
Solution
You have tell your backend engineer to provide you filter for including the writer's name in each object of the article. We use Loopback on our backend, which provides a filter for including the related model in each object internally.
Since your API calls have a lot of things in common, you should first set up an axios instance that re-uses those common features:
const api = axios.create({
baseURL: 'http://localhost:8000/api/',
headers: { Authorization: `Bearer ${localStorage.token}` }
});
Now since your MainBody needs to fetch the resources from the API asynchronously, there will be a short period where the data is not yet available. There are two ways you can handle this. Either the MainBody can be responsible for making all the calls, or it can be responsible for just making the call to get all the articles, then each of the ArticleView components can be responsible for getting the writer's name. I'll demonstrate the first approach below:
class MainBody extends Component {
state = { articles: null, error: null, isLoading: true };
async componentDidMount () {
try {
const response = await api.get('articles/');
const articles = await Promise.all(
response.data.map(async article => {
const response = await api.get(`writer/${article.created_by}/`);
return { ...article, writer_name: response.data.name };
})
);
this.setState({ articles, isLoading: false });
} catch (error) {
this.setState({ error, isLoading: false });
}
}
render () {
const { articles, error, isLoading } = this.state;
return isLoading ? 'Loading...' : error
? `Error ${error.message}`
: articles.map(article => (
<ArticleView
key={article.id}
article={article}
writer_name={article.writer_name}
/>
)
);
}
}

Any way at all to specify a callback to wrap the response with?

So I am trying to develop a search bar component in a React application where you can type in a users last name and that request will go to the Behance API and pull up that users data.
I am stuck on this:
axios
.get(API_URL + 'users?q=' + 'matias' + '&client_id=' + API_KEY + '&callback=')
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
alert(error.message);
});
I have tried wrapping the above in a const userSearch = () => {}, but that takes me a step farther from my goal. With the above I actually do get 200 statuses, but there is the CORS issue. I just can't seem to put together a callback that is not undefined in there, nevermind that this is a search bar implementation so I am going to have to refactor the above. I was just wanting to see some data returned.
One of the nicest things in Axios, is the seperation between request argument.
For example, the url should be only URL: API_URL + '/users'.
The parameters you want to pass, should be sent as an object.
The promise of the axios get, is the callback you are looking for.
Therefore, your request should look like this:
axios.get(API_URL + 'users', {
params: {
q: 'matias',
client_id: API_KEY,
}
})
.then(response => {
- success callback actions -
})
.catch(error => {
- error callback actions -
});
So I had refactored my axios code and I was still getting CORS errors, but after reading a couple of blogs online saying that with fetch() and jQuery you could get around that, in particular this SO article: Loading Data from Behance API in React Component
I actually duplicated Yasir's implementation like so:
import $ from 'jquery';
window.$ = $;
const API_KEY = '<api-key>';
const ROOT_URL = `https://api.behance.net/v2/users?client_id=${API_KEY}`;
export const FETCH_USER = 'FETCH_USER';
export function fetchUser(users) {
$.ajax({
url: `${ROOT_URL}&q=${users}`,
type: 'get',
data: { users: {} },
dataType: 'jsonp'
})
.done(response => {})
.fail(error => {
console.log('Ajax request fails');
console.log(error);
});
return {
type: FETCH_USER
};
}
And sure enough, no more CORS error and I get back the users data in my Network > Preview tabs. Not very elegant, but sometimes you are just trying to solve a problem and at wits end.

ReactJS recieving form errors from server

Below is my signup function in React. How can I render the errors it recieves from backend ? I tried to put a this.setResponse into the catch part ... that didn't work. I understand that componentWillReceiveProps should handle updates but this is an update from a Service (also below) not from a parent component.
If I print the err I can see a dictionary with errors, but I don't see how to pass them to the form or to the fields for rendering.
signup(evt) {
evt.preventDefault();
...
Auth.signup(dict)
.catch(function(err) {
alert("There's an error signing up");
console.log(err.response);
});
},
The Auth service is defined like this:
signup(dict) {
console.log(dict);
return this.handleAuth(when(request({
url: UserConstants.SIGNUP_URL,
method: 'POST',
type: 'json',
data: dict
})));
}
And I hoped to send errors to the fields with the following:
<UserNameField
ref="username"
responseErrors={this.responseErrors}
/>
Presuming that UserNameField is in the same component as the auth callback you can just set the state which will trigger a re-render and pass the values through UserNameField as props.
signup(evt) {
evt.preventDefault();
...
Auth.signup(dict)
.catch((err) => {
alert("There's an error signing up");
console.log(err.response);
this.setState({error: err});
});
},
<UserNameField
ref="username"
responseErrors={this.responseErrors}
error={this.state.error}
/>
This is the same as .bind(this).

Resources