How to increase axios speed? - reactjs

Because I'm new to using axios so I usually have a trouble in using it. Specifically, I'm making a react-infinite-scroll feature now, but when I compare its speed with other site, my post(react-infinite-scroll feature) is gonna be shown slowly a little. Then I'm thinking this problem is caused by 2 reasons
1. I'm not using axios properly
2. There is a thing makes axios speed urgrade, but I'm not using it
Here's my code, please give me some advice to increase my http request speed.
Thank you for reading my question!
class MainPage extends Component {
constructor(props) {
super(props)
axios.get("http://127.0.0.1:8000/api/question")
.then(res => {
this.setState({
AnswerPostMultiList: res.data
})
}
)
.catch(err => {
console.log(err)
})
}
state = {
AnswerPostMultiList : []
}
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener("scroll", this.handleScroll);
}
handleScroll = () => {
console.log("scroll is executing")
const { innerHeight } = window;
const { scrollHeight } = document.body;
const scrollTop =
(document.documentElement && document.documentElement.scrollTop) ||
document.body.scrollTop;
if (scrollHeight - innerHeight - scrollTop < 1000 && !this.props.isLoading["isLoading"]) {
this.props.onIsLoading() #To prevent this code from calling back continuously, change the value of this.props.isLoading["isLoading"] to false
axios.get("http://127.0.0.1:8000/api/question")
.then(res => {
this.setState({
AnswerPostMultiList: this.state.AnswerPostMultiList.concat(res.data)
})
this.props.onIsLoading() #change the value of this.props.isLoading["isLoading"] to true
}
)
.catch(err => {
console.log(err)
})
}
};
render() {
return(
<>
<PageHeader />
<div className="find_members">
{ this.state.AnswerPostMultiList.map((answerpost,index) => {
return <AnswerPostMulti question={answerpost.question_text} q_owner={answerpost.question_owner} answer={answerpost.answer_image} a_owner={answerpost.answer_owner} key={index} />
})
}
</div>
</>
)
}
}
const mapDispatchToProps = (dispatch) => ({
onIsLoading: () => {
dispatch(isLoadingActions.isLoading())
}
})
const mapStateToProps = state => ({
isLoading: state.isLoading
})
export default connect(mapStateToProps, mapDispatchToProps)(MainPage)

The best place to call a axios API calls is at componentDidMount(){}.
The Application loading process will be in this order skeleton->Initial rendering->later componentDidMount method is called. So here your app skeleton will be loaded and after that you can fetch data and use it to your app skeleton.
componentDidMount() {
axios.get("http://127.0.0.1:8000/api/question")
.then(res => {
this.setState({
AnswerPostMultiList: res.data
})
}
)
.catch(err => {
console.log(err)
});
}

Related

Why can't I setState the data I've successfully in React?

So I'm new to React, and having trouble fetching API. I've successfully fetched data object(I've checked it with console.log(), but somehow cannot setState it. Please see the code below. It's my full code.
import React, { Component } from 'react';
import EachCake from './EachCake';
class Cake extends Component {
constructor(props){
super(props);
this.state = {
}
}
componentDidMount() {
this._fetchApiEachCake();
}
_renderEachCake = () => {
return <EachCake
image={this.cake_object.image}
source={this.cake_object.source}
body={this.cake_object.body}
/>
}
_fetchApiEachCake = () => {
return fetch("http://127.0.0.1:8000/api/cake/3")
.then((response) => response.json())
.then(data => console.log(data))
.then(data => this.setState({cake_object : data}))
// .catch((err) => console.log(err))
}
render() {
return (
<div>
{this.state.cake_object ? this._renderEachCake() : "Loading this cake"}
</div>
)
}
}
export default Cake
For some reason, all I get on the screen is "Loading this cake". What do you think is the problem?
import React, { Component } from 'react';
import EachCake from './EachCake';
class Cake extends Component {
constructor(props){
super(props);
this.state = {
// 🔥 state initialization is optional also, useful for default values
}
}
componentDidMount() {
this._fetchApiEachCake();
}
_renderEachCake = () => {
return (
<EachCake
image={this.state.cake_object.image} // 🌟🌟
source={this.state.cake_object.source}
body={this.state.cake_object.body}
/>
)
}
_fetchApiEachCake = () => {
// 🔥 you can also remove return here
return fetch("http://127.0.0.1:8000/api/cake/3")
.then((response) => response.json())
.then(data => console.log(data) || data) // 🌟
.then(data => this.setState({cake_object : data}))
// .catch((err) => console.log(err))
}
render() {
return (
<div>
{this.state.cake_object ? this._renderEachCake() : "Loading this cake"}
</div>
)
}
}
export default Cake
🌟🌟 must be grabbed from the state not directly from this reference.
🌟 console.log doesn't return anything, so you must return data yourself oق combine setState and logging step both in one step e.g.
.then(cake_object => console.log(cake_object) || this.setState({ cake_object }))
The then() method returns a Promise.
if you are trying to check if the data is loaded or not you should use the callback this.setstate({key: value}, () => {//do something})
you can use this to set a flag whether data has been loaded into state or not. and i also think that you should initialize that cake_object to null.
so after that your code would be like:
this.state = {
loaded: false,
cake_object: null
}
_fetchApiEachCake = () => {
return fetch("http://127.0.0.1:8000/api/cake/3")
.then((response) => response.json())
.then(data => console.log(data))
.then(data => this.setState({cake_object : data}, () => {
console.log(this.state.cake_object);
this.setState({loaded: true});
}))
// .catch((err) => console.log(err))
}
render() {
return (
<div>
{this.state.loaded ? this._renderEachCake() : "Loading this cake"}
</div>
)
}
2 changes :
1.
this.state = {
cake_object:null,
}
_fetchApiEachCake = () => {
return fetch("http://127.0.0.1:8000/api/cake/3")
.then((response) => response.json())
.then((data) => {
console.log(data)
this.setState({cake_object : data})
})
// .catch((err) => console.log(err))
}
Hopefully it works!

context in componentDidMount appears as null

I currently have a context provider.
componentDidMount() {
if (this.state.memberID === null) {
try {
this.checkAuthUser();
} catch (e) {
console.error(e);
}
}
}
checkAuthUser = () => {
new Promise((resolve, reject) => {
this.props.firebase.auth.onAuthStateChanged(authUser => {
if(authUser) {
resolve(authUser);
} else {
reject(new Error("Not authorized"));
}
})
})
.then( authDetails => {
this.props.firebase.getOrgID(authDetails.uid).on('value', snapshot => {
const setSnapshot = snapshot.val();
const getOrganizationID = Object.keys(setSnapshot)[0];
this.setState({ memberID: authDetails.uid, orgID: getOrganizationID })
})
})
.catch(err => console.log(err))
}
When I try to use this in another component:
static contextType = AuthDetailsContext;
componentDidMount() {
console.log('here is context: ' + this.context.orgID);
if(this.context.orgID) {
this.setState({currentOrganization: this.context.orgID, loading: true}, () => {
this.getMembersInDB('1');
})
}
}
My console.log is null. Means the context isn't registering yet. Any idea what I'm doing wrong?
Your design here seems flawed i.e. when your provider is mounted you send the API request and then when your descendant component is mounted you try to use it - these operations will happen in quick succession, far quicker than it would take for an API call to return from a server.
In your provider, if you must have a user before the component mounts then you need to delay rendering the child components until your API response completes i.e.
const AuthDetailsContext = React.createContext(null);
class AuthDetailsProvider extends PureComponent {
...
componentDidMount() {
const { firebase } = this.props;
firebase.auth.onAuthStateChanged(authUser => {
if (!authUser) {
// Maybe set some other state state to inform the user?
this.setState({ authError: new Error('Not Authorised') });
return;
}
firebase.getOrgID(authUser.uid)
.on('value', snapshot => {
const setSnapshot = snapshot.val();
const getOrganizationID = Object.keys(setSnapshot)[0];
this.setState({
authError: null,
memberID: authUsermemberID.uid,
orgID: getOrganizationID
});
});
})
}
render() {
if (this.state.authError) return <b style={{ color: red }}>{this.state.error.message}</b>;
if (!this.state.memberID) return <b>Authenticating...</b>
return (
<AuthDetailsContext.Provider value={this.state}>
{this.props.children}
</AuthDetailsContext.Provider>
);
}
}

setInterval calls in functional components using hooks in ReactJS

In a ReactJs app, setInterval is being used in the demo below. Currently, it waits for 10 seconds even at inital load, instead, it should call immediately then after 10 second it should call again. How can i achieve this?
const callApiAfterOneSec = async () => {
await axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then(res => {
setLoading(false);
setPost(res.data);
})
.catch(err => {
setLoading(false);
console.log(err);
});
};
useEffect(() => {
const interval = setInterval(callApiAfterOneSec, 10000);
return () => {
clearInterval(interval);
};
}, [post]);
Try to add another useEffect(), fired only once:
useEffect(() => {
callApiAfterOneSec();
}, []);
You could use this as class component
class App extends React.Component {
interval;
constructor() {
super()
this.state = {
data: {}
}
}
componentDidMount() {
this.loadData();
this.interval = setInterval(this.loadData, 5000);
}
componentWillUnmount() {
clearTimeout(this.interval);
}
async loadData() {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/posts/1');
await res.json().then(res => {
this.setState({
data: res
})
})
} catch (e) {
console.log(e);
}
}
render() {
return (
<div>
<ul>
<li>UserId : {this.state.data.userId}</li>
<li>Title : {this.state.data.title}</li>
<li>Body : {this.state.data.body}</li>
</ul>
</div>
);
}
}
render(<App />, document.getElementById('root'));
As #xadm mention you can useEffect two times as per requirement simple fix
useEffect(() => {
callApiAfterOneSec();
}, []);
This is just a hack that might solve you problem.
const [rendered, setRendered] = useState(false);
useEffect(() => {
let interval = null;
if(rendered) interval = setInterval(callApiAfterOneSec, 10000)
setRendered(true);
return () => {
clearInterval(interval)
}
}, [post, rendered])

Not updating the state instantly

So, when I click on the add to cart button on the Screen2, it logs articleToCart aka cartArticle as empty array... Only when I go back to Screen1 and than again on the Screen2, pressing add to cart button again it logs cartArticle array with one item even though add to cart button was clicked 2x... How can I make it that when I click on add to cart button, it updates the state immediately? What am I doing wrong? I am using react navigation v2. Is it possible to setState trough params and that to be instant not like this, with delay?
class Screen1 extends Component {
state = {
articles: {
article: [],
},
cartArticle: []
};
articleToCart = () => {
this.setState(prevState => {
return {
cartArticle: prevState.cartArticle.concat(prevState.articles.article)
};
});
};
qrCodeOnReadHandler = ({ data }) => {
fetch(data)
.then(response => response.json())
.then(json => [
console.log(json),
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
}),
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
])
.catch(err => {
alert("Nesto ne valja. Pokusajte ponovo!");
console.log(err);
});
};
render() {
return (
);
}
}
Second screen
class Screen2 extends Component {
addToCartHandler = () => {
const { navigation } = this.props;
const articleToCart =navigation.getParam("articleToCart","Nedostupno");
const add = navigation.getParam("addToCartOnPress", "Nedostupno");
console.log(articleToCart);
add();
};
goBackHandler = () => {
this.props.navigation.goBack();
};
render() {
return (
<View style={styles.buttons}>
<CustomButton color="#1DA1F2" onPress={this.goBackHandler}>
Back
</CustomButton>
<CustomButton color="#1DA1F2" onPress={this.addToCartHandler}>
Add to Cart
</CustomButton>
);
}
}
in your qrCodeOnReadHandler on screen1:
[
console.log(json),
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
}),
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
]
you are returning an array with your functions as its indices.
try changing it to this instead.
{
console.log(json);
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
});
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
}
setState is async, you can't read values just after setting them .. but you can use setState callback, sth. like this:
.then(json => {
console.log(json)
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
}, () => {
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
})
})

Using one event to trigger multiple ajax requests in redux

I am writing a metrics page using React-Redux, which I haven't used before, and am having trouble structuring it.
The basic structure is something like this:
<input id=start_date />
<input id=end_date />
<button id=submit onClick={ this.props.fetchChartData() }/>
<Chart1 />
<Chart2 />
The store structure is this:
dates
start_date: "2016-09-16"
end_date: "2016-09-16"
charts
Chart1
api_func: "get_supported_events"
fetching: false
fetched: false
data: null
error: null
Chart2
api_func: "get_events_closed"
fetching: false
fetched: false
data: null
error: null
Using thunk, my actions right now include these functions:
function getStateURL(state){
return state.charts.Chart1['api_func'];
}
export function fetchChartData(){
return (dispatch, getState) => {
dispatch(fetchChartDataStart());
return fetch(getStateURL(getState()))
.then((response) => response.json())
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err)));
}
}
The problem is, I don't want to hard code the chart name because I feel like I should be able to write one action since all of the charts need to do the same thing.
The best solution I could guess is to have the button trigger an event that the chart components could listen for so that when the state is requested it is limited to the chart's portion, not the entire state. Is there a way to make a react component trigger an event that can be caught by other components?
The solution you are proposing seems more like old Flux model when store was just an instance of EventEmitter.
Using Flux, you can make <Chart /> like
class Chart extends Component {
componentDidMount() {
store.addEventListener('fetchData', this.fetchData);
}
componentWillUnmount() {
store.removeEventListener('fetchData', this.fetchData);
}
this.fetchData() {
api.fetchChartData(store.get('chart1.url');
}
render() {
...
}
}
With Redux however it is not immediately obvious. But it is possible to do it:
class Chart1 extends Component {
componentWillReceiveProps(nextProps) {
if (!nextProps.fetching && !nextProps.fetched) {
const { fetchData, url } = this.props;
fetchData(url);
}
}
render() {
...
}
}
export default connect(state => ({
fetching: state.Chart1.fetching
fetched: state.Chart1.fetched
url: state.Chart1.url
}), {
fetchData
})(Chart1)
and in /action.js
export function fetchChartData(url){
return (dispatch) => {
dispatch(fetchChartDataStart());
return fetch(url)
.then((response) => response.json())
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err)));
}
}
Considering the similar functionalities in all the <Chart /> components, it's worth implementing Higher order component for this and keep url somewhere as constants rather than in store.
export const fetchData = (url) => (Wrapped) => {
class Wrapper extends Component {
componentWillReceiveProps(nextProps) {
if (!nextProps.fetching && !nextProps.fetched) {
const { fetchData, url } = this.props;
fetchData(url);
}
}
render() {
return <Wrapped {...this.props} />
}
}
return connect(null, { fetchData })(Wrapper);
}
In Chart.jsx use it like:
import { chart1Url } from '.../someconstants';
import { fetchData } from '/hocs/fetchData'
const Chart1 = () => {
return <div>...</div>;
}
export default fetchData(chartUrl)(Chart1);
Although it is possible, I still think the best solution would be to store URLs in a constants file, and put api functions in another module. You can do something like:
./api/fetchData.js
export function fetchData(url) {
return new Promise((resolve, reject) =>
fetch(url)
.then((response) => response.json())
.then((json) => resolve(json))
.catch((err) => reject(err));
}
./actions.js
import { fetchData } from '../api/fetchData';
import { urls } from '.../constants';
export function fetchChartData(){
return (dispatch) => {
dispatch(fetchChartDataStart());
return Promise.all(urls.map((url) =>
fetchData(url)
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err))));
}
}

Resources