I have been trying to use axios CancelToken with react. But the cancel token has no effect whatsoever.
A brief about the code:
There is an input, and in the onChange event of the input fires the getData() function.
I want this to NOT send out an api request everytime the user presses the keys but only at the end or maybe after a timeout.
Please let me know what I'm doing wrong.
import React from 'react';
import axios from 'axios';
export default function Home() {
let axiosCancelToken;
async function getData() {
if (axiosCancelToken) {
axiosCancelToken.cancel();
console.warn('request cancelled')
}
console.info('no cancel', axiosCancelToken)
axiosCancelToken = axios.CancelToken.source();
let val = await axios({
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/todos/1',
cancelToken: axiosCancelToken.token
})
}
...
And the <input.../>
<input type="text" onChange={getData} />
I guess it doesn't work because you are trying to cancel the wrong token.
Let's look at this example:
export default function Home() {
let axiosCancelToken;
async function getData() {
if (axiosCancelToken) {
axiosCancelToken.cancel();
}
axiosCancelToken = axios.CancelToken.source();
let val = await axios({
method: "GET",
url: "https://jsonplaceholder.typicode.com/todos/1",
cancelToken: axiosCancelToken.token
});
}
return (
<div>
<button onClick={getData}>Get Data</button>
</div>
);
}
Try clicking the button twice quickly, you will see the first request canceled as expected.
But if I change the code by adding some React state, it won't work anymore:
export default function App() {
let axiosCancelToken;
let [count, setCount] = useState(0);
async function getData() {
setCount((count) => count + 1);
if (axiosCancelToken) {
axiosCancelToken.cancel();
console.warn("request cancelled");
}
axiosCancelToken = axios.CancelToken.source();
let val = await axios({
method: "GET",
url: "https://jsonplaceholder.typicode.com/todos/1",
cancelToken: axiosCancelToken.token
});
}
return (
<div>
<button onClick={getData}>Get Data {count}</button>
</div>
);
}
And the reason for that is that when React has to re-render, it will call your function component again, where your axiosCancelToken will be instantiated again in the new scope.
To solve this you can just use React Refs.
let axiosCancelToken = useRef();
Related
I am trying to learn react, and I am making a successful API call, but it only prints in the console. I found examples but many of them recommended to use setData(json) but I am not able to use it because the file is a list of export async function which was also recommended.
export async function GetHellWorld() {
return fetch(`http://localhost:8080/api`, {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).then(response => response.json())
.then(json => {
console.log(json)
})
.catch(error => (console.log(error)))
}
and the component
function Test(thisArg, argArray) {
const result = GetHellWorld.apply()
return (
<div className="App">
{JSON.stringify(result)}
</div>
);
}
export default Test;
In the console I see "Hello World" but in the browser is get just {}.
Two questions:
How can I bind the JSON response to an object so I can do something like result.name.
Is this the correct was to call the await function? const result = GetHellWorld.apply()
---- update ----
I decided to try axios because I want to make multiple calls in one file.
const axios = require('axios');
export class AppService {
public async GetHelloWorld(): Promise<any> {
const response = await axios.get(`http://localhost:8080/api`, {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).catch(() => console.log("Issue in GetHelloWorld"))
return response.data
}
}
component
import React from 'react';
import {AppService} from "../services/app.service";
function Movies() {
const api = new AppService()
const hello = async () => {
const response = await api.GetHelloWorld();
console.log("The response: " + response)
}
return (
<div className="App">
{JSON.stringify(hello)}
</div>
);
}
note I had to add typescript support.
For whatever reason I get
Module not found: Error: Can't resolve '../services/app.service' in '/Users/miketye/programming/test-react/src/components'
While the other answer about using a custom hook can work, I would not recommend it while you're still leaning React.
Look up how to use the "useEffect" hook, that's generally how you want to do any sort of loading logic in React.
First off, you need to fix your async function so it actually returns a value:
// style/convention note, but non-component functions should not start with a capital letter
export async function getHelloWorld() {
return fetch(`http://localhost:8080/api`, {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).then(response => response.json())
.then(json => {
return json // will cause this function to return a Promise of type "string", since we're in an async function
})
// better to just let the error get thrown here, for testing
}
Then use it like this:
function Test(thisArg, argArray) {
[fetchResult, setFetchResult] = useState(undefined) // look up useState. State is how you have values that change over time in a resct component
useEffect(() => {
async function fetchData() {
const data = await getHelloWorld()
setFetchResult(data)
}
fetchData()
}, [])
// look up useEffect. Since the second argument (the "dependency array") is empty, useEffect will fire only once, after the component loads
return (
<div className="App">
{result ? JSON.stringify(result) : "no result yet"}
</div>
);
}
export default Test;
You can use a custom hook for this purpose:
import { useState } from "react";
const useFetchData = () => {
const [data, setData] = useState(null);
const fetchData = () => {
fetch("http://localhost:8080/api", {
method: "Get",
headers: {
"Content-type": "application/json; charset=UTF-8"
}
}).then(response => response.json())
.then(json => { setData(json); })
.catch(error => { console.log(error); })
}
useEffect(() => {
fetchData();
}, []);
return { data, fetchData };
}
export default useFetchData;
And then call it in your component:
import useFetchData from "#/hooks/useFetchData";
const Test = () => {
const { data, fetchData } = useFetchData();
// CALL fetchData IF YOU WANT TO UPDATE THE CURRENT STATE
return (
<div className="App">
{data && JSON.stringify(data)}
</div>
);
}
export default Test;
I am currently working with a yarn React application, and one of my front-end components has a form that sends a get request to the backend.
import { useState } from "react";
import axios from "axios";
export default function fld(props){
//set the inital state of the name as null
const [songName, setSongName] = useState(null);
//this function is linked to the GET request,
const handleRetrieve = async (event) =>{
//prevent the page from reloading
event.preventDefault();
//set the formData
const formData = new FormData();
formData.append("songName", songName)
try{
const response = await axios({
method: "get",
url: "http://localhost:8080/",
data: formData
});
console.log(response)
}catch(error){
console.log(error)
}
};
const handleNameSelect = (event ) =>{
setSongName(event.target.name[0]);
};
return(
<form onSubmit={handleRetrieve}>
<label>List out the songs</label>
<input type="text" onChange={handleNameSelect}/>
<input type="submit"/>
</form>
)
}
The thing is, I have multiple get requests in the backend, but only want to fire off the second one.
FIRST GET REQUEST:
app.get("/", (req, res) => {
res.sendFile(__dirname + "/index.html");
});
SECOND GET REQUEST (the one I want to fire)
app.get("/Uploadedfiles/:name", (req, res) => {
console.log("GET method: Uploadedfiles/:name")
const params = req.params.name;
let red = read(params);
console.log("reading from folder");
res.send(red);
});
help pls :(
The only thing you need is to add the endpoint path to the config object in axios like this:
const response = await axios({
method: "get",
url: `http://localhost:8080/Uploadedfiles/${songName}`,
});
Another way to use axios is to use the implicit methods like this:
const response = await axios.get(`http://localhost:8080/Uploadedfiles/${songName}`);
For a complete list of examples on different ways to use axios here is their official docs axios
I am using DRF for creating the api.. I am able to fetch the data using axios, but it returns undefined the first time and hence, when I use useState, it gets set as undefined..
ItemDetail.js:
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const ItemDetail = () => {
const [detail, setDetail] = useState('')
const [id, setId] = useState('')
const RecipeDetail = async () => {
const res = await axios({
method: 'get',
url: `http://127.0.0.1:8000/api/recipe-detail/1`,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
"Access-Control-Allow-Origin": "*",
}
})
setDetail(res.data)
}
useEffect(() => {
RecipeDetail()
}, [id])
console.log(`Hi ${detail}`)
return (
<div>
Hi
</div>
)
}
export default ItemDetail
So why is the API returning undefined for the first time?
I read few answers regarding the use of async/await which I have.. Also, when I console.log(detail), it logs it multiple times.. Why is that so?
As u can see in the image, it logs multiple times..
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const ItemDetail = () => {
const [detail, setDetail] = useState('');
const RecipeDetail = async () => {
const res = await axios({
method: 'get',
url: 'http://127.0.0.1:8000/api/recipe-detail/1',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Access-Control-Allow-Origin': '*',
},
});
setDetail(res.data);
};
useEffect(() => {
RecipeDetail();
}, [detail]);
console.log(`Hi ${detail}`);
return (
<div>
{detail ? 'Hi' : 'Loading ... '}
</div>
);
};
export default ItemDetail;
You are trying to access or log the detail before it is set ,means that useEffect will be called after your content is rendered so console.log(Hi ${detail}); ran first and undefined was logged , later useEffect ran and RecipeDetail(); data received state changed as you called setState. and rerender occured again and this time you received value.
When it was first rendered, no API response was received yet.
and then, when second rendered, API resonse was receiced.
I was trying to create a custom Hooks for handling input HTTP request from any component by simply calling the useCustomHooks but its getting failed and error is
Can not use keyword 'await' outside an async function
All i made is a handler that triggers http request custom component method
import { useState } from 'react';
import axios from "axios";
const useHttpReqHandler = () => {
const [result, setResult] = useState();
const apiMethod = async ({url , data , method}) => {
let options = {
method,
url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
data
};
let response = await axios(options);
const UpdatedData = await response.data;
console.log(UpdatedData)
setResult(UpdatedData);
}
return [result, apiMethod];
};
export default useHttpReqHandler;
Now i can use this hook in my code and on any event handler just call callAPI returned from the hook like this
const MyFunc = () => {
const [apiResult, apiMethod] = useHttpReqHandler();
const captchValidation = () => {
const x = result.toString();;
const y = inputValue.toString();;
if ( x === y) {
apiMethod({url: 'some url here', data: {"email": email}, method: 'post'});
alert("success")
}
else {
alert("fail")
}
}
Is is a correct approch ? as i am beginner in Reactjs
Here is a working version:
useHttpReqHandler.jsx
import { useState } from 'react';
import axios from "axios";
const useHttpReqHandler = () => {
const [apiResult, setApiResult] = useState();
const apiMethod = async ({url , data , method}) => {
let options = {
method,
url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
data
};
let response = await axios(options);
let responseOK = response && response.status === 200 && response.statusText === 'OK';
if (responseOK) {
const data = await response.data;
console.log(data)
setApiResult(data);
}
}
return [apiResult, apiMethod];
};
export default useHttpReqHandler;
What's important here:
await is called inside an async function (apiMethod)
The result is stored in a local state (apiResult)
The function returns an array [apiResult, apiMethod]
How to use it:
const [apiResult, apiMethod] = useHttpReqHandler();
apiMethod({url: 'some url here', data: {"email": email}, method: 'post'});
Render the result:
return {apiResult};
In my opinion, it is better to use .then with Axios. and try to create for each method different functions "Get/Post...", why because in the GET method you need to useEffect, but it can not be the same case in POST method. in GET method useHttpReqHandler.js
import { useEffect, useState } from "react";
import axios from "axios";
// GET DATA
const useHttpReqHandler = (url) => {
const [httpData, setHttpData] = useState();
useEffect(() => {
axios
.get(url)
.then((axiosData) => {
// Axios DATA object
setHttpData(axiosData.data);
// you can check what is in the object by console.log(axiosData);
// also you can change the state, call functions...
})
.catch((error) => {
console.log(error);
});
}, []);
return httpData;
};
export default useHttpReqHandler;
in your main file
import useHttpReqHandler from "...."
const MyFunc = () => {
const getData = useHttpReqHandler("URL");
return (
<div>
...
</div>
)
}
I hope it helps
the same thing will be with POSt, PUT, DELETE ... you will create functions for each method that will handle the Http req
I am trying to request an API request using hooks. But my problem is that my function is called before I onPress.
I have an custom API component like this:
const FetchDataPut = (URL) => {
useEffect(() => {
async function fetchData() {
const res = await axios({
method: 'put',
url: URL,
data:{
name:'111',
email:'222',
password:'333',
id:'444',
phone:'555'
}
});
const response = await res;
console.log(response, 'completed')
}
fetchData()
},[])
return null
}
I could see from the console.log that api request has completed. My problem is that my api component is called before I onPress the button.
This is my Button component:
const EditAccount = (props) => {
const Desktop = () => {
const URL = `MyURL...`
return (
<View>
<Button title='edit account' onPress={FetchDataPut(URL)}/>
</View>
)
}
return(
<div>
<Desktop/>
</div>
)
}
If I change my onPress function to an arrow function like this:
onPress={()=>FetchDataPut(URL)} component isn't called before I onPress it. But it will give me an error Invalid hook call. Hooks can only be called inside of the body of a function component
Any Idea how to my the api request when I onPress the Button ?
Any comments or advice would be really helpful. Thanks in advance! :)
I think the way to go is to use a hook rather than a component:
const useFetchDataPut = () => {
const [url, setUrl] = useState("");
useEffect(() => {
if (!url) return;
async function fetchData() {
const res = await axios({
method: "put",
url,
data: {
name: "111",
email: "222",
password: "333",
id: "444",
phone: "555"
}
});
const response = await res;
console.log(response, "completed");
}
fetchData();
}, [url]);
return setUrl;
};
And then call it when you press the button. Also Desktop should be defined outside of the EditAccount component.
const Desktop = () => {
const setUrl = useFetchDataPut();
return (
<View>
<Button
title="edit account"
onPress={() => setUrl("https://...")}
/>
</View>
);
};
const EditAccount = props => {
return (
<div>
<Desktop />
</div>
);
};
Let me know if something is not clear.
There are several issues here.
You’re setting the onPress prop to the result of calling FetchDataPut(URL). The prop should be the function itself, not the result of invoking it. By using an arrow function you’re declaring a new function that, when invoked, calls FetchDataPut.
If you’re invoking it on button press, there’s no need for it to be a hook.
Also, FetchDataPut isn’t a React component.
Declare your data fetching function by itself:
async function fetchData(url) {
return axios({
method: 'put',
url: URL,
data: {
name:'111',
email:'222',
password:'333',
id:'444',
phone:'555'
}
})
}
And then invoke it on button press.
const handler = async function ( ) {
// or just inline the axios request right here
// instead of declaring a separate function for it.
const result = await fetchData(url);
// do something with the result
}
<Button onPress={handler} />
Apologies for the frequent edits. Trying to do this from my phone.