Axios "uncancel" api calls - reactjs

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;

Related

Redux-saga: Best way to handle async call data used in only one component

I need advice concerning redux-saga and the way to handle async call. I don't find anwsers to my questions.
I would like to know how can I handle properly async API call which return data used in only one component (so useless to store it in the store) ?
In my react application, I use redux-saga to handle async call. When the saga finish correctly, I dispatch a success action which store result in the store.
However, i find useless to store the result when I only want to display it in one component. Instead I would like to run a saga and return by a callback data to my component without storing it int the store. Is it possible ? How can I do that ?
thanks.
Here is a sample code for you, that code makes an api request in componentDidMount lifecycle and sets the data to its state and after it renders it.
import React, { Component } from "react";
import { render } from "react-dom";
import axios from 'axios';
class App extends Component {
constructor() {
super();
this.state = {
data: []
};
}
async componentDidMount() {
try {
let response = await axios.get('https://jsonplaceholder.typicode.com/users');
console.log('response.data: ', response.data);
this.setState({
data: response.data
});
} catch (error) {
console.log('error: ', error);
}
}
render() {
return (
<ul>
{this.state.data.map(item => <li>{item.name}</li>)}
</ul>
);
}
}
Hope this helps.

Axios interceptor not working when initialized in a react Higher Order Function

I have a Component which makes a axios get
Component <using axios instance> {
componentDidMount(){
axios.get('myurl.jn').then(ingredients => { <wrong url>
}).catch( error => {
console.log(error) // dosent even show here as we donot propagate the error
})
}
}
export default hoc(Component,axiosInstance)
Higher Order Funcfor Component
const ErrorHandler = (WrapperComponent, axios) => {
return class extends Component {
componentDidMount(){
axios.interceptors.request.use(req => {
console.log(req) // none
this.setState({error : null});
return req;
});
axios.interceptors.response.use(res => res,(error) => {
console.log(error) // none
this.setState({error : true});
});
}
I tried adding logger and breakpoints to all the places in the hoc which uses the axios instance but to no effect nothing gets called .
The only place the interceptor is picked up in the place where i create the instance why and how do we solve this. The console for browser throws 404 but not caught by interceptor.
I am very new to react and axios just learning for past week please pardon if not following any proper guidelines

Return Fetched Data from Axios in React

Have a question about rendering fetched data with Axios. I’m able to log returned data to the console, however, it will not render on the screen.
I’m using an NPM Bitly module: https://www.npmjs.com/package/bitly
const BitlyClient = require('bitly');
const bitly = BitlyClient('ACCESS TOKEN');
State
class Example extends Component {
constructor() {
super();
this.state = {
landing: 'https://www.google.com/',
newUrl: 'https://www.udacity.com/'
};
API Call
componentDidMount() {
bitly.shorten(this.state.landing)
.then((response) => {
this.setState({newUrl: response.data.url })
console.log(response.data.url);
})
.catch(function(error) {
console.error(error);
});
}
This returns data to the console but does not render to the page.
Render to Page
<Component> {this.newUrl} </>
What am I missing?
It should be {this.state.newUrl}.
Literally started to work as soon as I posted this smh.
I just updated the component to include the state.
Did not work
<Component> {this.newUrl} </>
Does Work
<Component> {this.state.newUrl} </>

mapStateToProps does not set the data in component props

I'm just getting started with React. I successfully used axios to get data from http and use an action to push the data. I can output the data at mapStateToProps but it does not set the data as a prop in the class. Here's my code with comments about the availability of the data.
import React from 'react';
import { connect } from 'react-redux';
import { fetchCountries } from '../../actions/actions';
import _ from 'lodash';
class TheClass extends React.Component
{
constructor(props)
{
super(props);
}
componentDidMount()
{
this.props.fetchCountries();
console.log('Fetching', this.props.countries); // !! UNDEFINED !!
}
}
function mapStateToProps(state)
{
console.log('Countries:', state.countries) // -> I get the data
return { countries: state.countries }
}
export default connect(mapStateToProps, { fetchCountries })(TheClass);
actions.js
import axios from 'axios';
export const FETCH_COUNTRIES = `fetch_countries`;
const COUNTRIES_URL = `http://api.stagingapp.io/location/v1/public/country`;
export function fetchCountries()
{
const request = axios.get(COUNTRIES_URL);
console.log(request); // -> I get the data
return {
type: FETCH_COUNTRIES,
payload: request
}
}
fetchCountries is an asynchronous operation so you can't expect the result just after calling fetchCountries as you are trying to do in componentDidMount.
If you are getting the result in connect function, then you will get the result in render function after successful network call.
Put your console here:
render() {
console.log('Fetching', this.props.countries);
}
I'd imagine that state.countries gets populated by whatever response you get from your asynchronous HTTP request in fetchCountries().
Only once this request resolves, should you get the country data. When you call fetchCountries() and immediately afterwards try to print out the value of countries, the request has not yet resolved (gotten a response), which is why you wont get any data.
Your fetch countries request in Asynchronous request, so you can't expect countries to be in store just after calling the fetchCountries() function. You will get countries data when react will re render on arrival of countries data from api.
Your function getCountries return an object with payload = a Promise return by axios, so you don't have your data when you call the function.
To make Async request you should add redux-thunk middleware, after that in your component file create a function
const mapStateToProps = (dispatch) => ({
fetchCountries: bindActionsCreator(fetchCountries, dispatch)
})
and pass this function in 2nd argument to your connect function.
In your actions.js change your function getCountries like so:
export const fetchCountries = () => (dispatch) => {
dispatch({type: FETCH_START})
axios.get(COUNTRIES_URL)
.then(response => response.data)
.then(data => dispatch({type: FETCH_COUNTRIES, payload: data})
.catch(errors => dispatch({type: FETCH_ERRORS})
}
With that, in your reducer you can set a variable loading to true when request start and pass this variable to false when Promise is resolved/rejected and after that you can create a condition to your component to be sure you have your data!

Invariant Violation: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch

I am using ALT for my ReactJS project. I am getting the cannot 'dispatch' error if the ajax call is not yet done and I switch to another page.
Mostly, this is how my project is setup. I have action, store and component. I querying on the server on the componentDidMount lifecycle.
Action:
import alt from '../altInstance'
import request from 'superagent'
import config from '../config'
import Session from '../services/Session'
class EventActions {
findNear(where) {
if (!Session.isLoggedIn()) return
let user = Session.currentUser();
request
.get(config.api.baseURL + config.api.eventPath)
.query(where)
.set('Authorization', 'Token token=' + user.auth_token)
.end((err, res) => {
if (res.body.success) {
this.dispatch(res.body.data.events)
}
});
}
}
export default alt.createActions(EventActions)
Store
import alt from '../altInstance'
import EventActions from '../actions/EventActions'
class EventStore {
constructor() {
this.events = {};
this.rsvp = {};
this.bindListeners({
findNear: EventActions.findNear
});
}
findNear(events) {
this.events = events
}
}
export default alt.createStore(EventStore, 'EventStore')
Component
import React from 'react';
import EventActions from '../../actions/EventActions';
import EventStore from '../../stores/EventStore';
import EventTable from './tables/EventTable'
export default class EventsPage extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
events: [],
page: 1,
per: 50
}
}
componentDidMount() {
EventStore.listen(this._onChange.bind(this));
EventActions.findNear({page: this.state.page, per: this.state.per});
}
componentWillUnmount() {
EventStore.unlisten(this._onChange);
}
_onChange(state) {
if (state.events) {
this.state.loading = false;
this.setState(state);
}
}
render() {
if (this.state.loading) {
return <div className="progress">
<div className="indeterminate"></div>
</div>
} else {
return <div className="row">
<div className="col m12">
<h3 className="section-title">Events</h3>
<UserEventTable events={this.state.events}/>
</div>
</div>
}
}
}
componentDidMount() {
EventStore.listen(this._onChange.bind(this));
EventActions.findNear({page: this.state.page, per: this.state.per});
}
This would be my will guess. You are binding onChange which will trigger setState in _onChange, and also an action will be fired from findNear (due to dispatch). So there might be a moment where both are updating at the same moment.
First of all, findNear in my opinion should be as first in componentDidMount.
And also try to seperate it in 2 differnet views (dumb and logic one, where first would display data only, while the other one would do a fetching for example). Also good idea is also to use AltContainer to actually avoid _onChange action which is pretty useless due to the fact that AltContainer has similar stuff "inside".
constructor() {
this.events = {};
this.rsvp = {};
this.bindListeners({
findNear: EventActions.findNear
});
}
findNear(events) {
this.events = events
}
Also I would refactor this one in
constructor() {
this.events = {};
this.rsvp = {};
}
onFindNear(events) {
this.events = events
}
Alt has pretty nice stuff like auto resolvers that will look for the action name + on, so if you have action called findNear, it would search for onFindNear.
I can't quite see why you'd be getting that error because the code you've provided only shows a single action.
My guess however would be that your component has been mounted as a result of some other action in your system. If so, the error would then be caused by the action being triggered in componentDidMount.
Maybe try using Alt's action.defer:
componentDidMount() {
EventStore.listen(this._onChange.bind(this));
EventActions.findNear.defer({page: this.state.page, per: this.state.per});
}
I believe it's because you're calling an action, and the dispatch for that action only occurs when after the request is complete.
I would suggest splitting the findNear action into three actions, findNear, findNearSuccess and findNearFail.
When the component calls findNear, it should dispatch immediately, before even submitting the reuqest so that the relevant components will be updated that a request in progress (e.g. display a loading sign if you like)
and inside the same action, it should call the other action findNearSuccess.
The 'Fetching Data' article should be particularly helpful.

Resources