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.
Related
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();
i have state vacations, i set it after fetch within useEffect, i have button approve that will change data in vacation state and i want to re-render component after that happens within function handleApprove , so i made up virtual state componentShouldUpdate with initial value of false and passed it as a dependency for useEffect, and when function handleApprove gets triggered, i setState to the opposite of its value !componentShouldUpdate, but the component only re-render when i click 2 times, why is that happening and why it works fine when i setState componentShouldUpdate from a child component ?
function VacationsComponent() {
const [vacations, setVacations] = useState([{}]);
const [componentShouldUpdate, setComponentShouldUpdate] = useState(false);
useEffect(() => {
const getVacations = async () => {
const response = await fetch("http://localhost:8000/get-vacations");
const data = await response.json();
setVacations(data);
};
getVacations();
}, [componentShouldUpdate]);
const handleApprove = async (e, vactionId) => {
(await e.target.value) === "approve"
? fetch(`http://localhost:8000/approve-vacation/${vactionId}`, {
method: "POST",
})
: fetch(`http://localhost:8000/reject-vacation/${vactionId}`, {
method: "POST",
});
setComponentShouldUpdate(!componentShouldUpdate);
};
<button onClick={(e) => handleApprove(e, item._id)}>
APPROVE
</button>
}
This is most probably caused because useState hook operates asynchronously. Read more here.
You can update your code to use only one state like this
function VacationsComponent() {
const [vacations, setVacations] = useState([{}]);
const getVacations = async () => {
const response = await fetch("http://localhost:8000/get-vacations");
const data = await response.json();
setVacations(data);
};
useEffect(() => {
getVacations();
}, []);
const handleApprove = async (e, vactionId) => {
const slug =
e.target.value === "approve" ? "approve-vacation" : "reject-vaction";
await fetch(`http://localhost:8000/${slug}/${vactionId}`, {
method: "POST",
});
getVacations();
};
<button onClick={(e) => handleApprove(e, item._id)}>APPROVE</button>;
}
put the setComponentShouldUpdate(!componentShouldUpdate) inside a thenable like this, and remove the async/await construct.
Also what was the intended purpose for setting state, I don't see the boolean being used anywhere. Usually when setting state you want the DOM to be updated somewhere, and especially with a boolean its great for toggling elements on the screen.
const handleApprove = (e, vactionId) => {
e.target.value === "approve"
? fetch(`http://localhost:8000/approve-vacation/${vactionId}`, {
method: "POST",
}).then(()=>{
// does this go here if it is approved or when it s rejected
setComponentShouldUpdate(!componentShouldUpdate);
})
: fetch(`http://localhost:8000/reject-vacation/${vactionId}`, {
method: "POST",
}).then(()=>{ setComponentShouldUpdate(!componentShouldUpdate); });
};
What I have done by far is when a user creates a client, in the top right of the page, is shown a flag(notification), which says "Client has been successfully created".
To do that was a little complex for me, because saving the client to DB, and listing the client to the web page are in two different components. Also, the flag is another component as well.
To save and list the clients I have used Axios since I'm dealing with the backend a lot.
SaveClient.js
export default function SaveClient({}) {
const save = async () => {
const clientParams = {
userName:
currentClient: clientName,
clientId: clientId,
};
await axios
.post(
process.env.REACT_API_CLIENT, clientParams
)
.then((response) => {
navigate("/clientlist", {state: {showFlagCreate: true}}); //passing the state
})
.catch((error) => {;
console.log(error);
});
};
}
ClientList.js
export default function ClientList() {
const { state } = useLocation();
const showFlagCreate = state?.showFlagCreate;
const [clientlist, setClientList] = useState([])
useEffect(() => {
const clientParams = {
userName:
currentClient: clientName,
clientId: clientId,
};
axios
.get(process.env.REACT_API_CLIENT, clientParams)
.then((response) => {
const {data} = response
setClientList(data)
})
.catch((error) => console.log(error));
}, []);
return (
<div>
...
{showFlagCreate && <FlagCreateClient />}
</div>
);
}
FlagCreateClient
export default function FlagCreateClient() {
const [show, setShow] = useState(true);
return (
<div>
<Transition
show={show}
as={Fragment}
<div>
<p>The client is successfully created.</p>
</div>
<div>
<button onClick={() => {setShow(false)}}>
<span>Close</span>
</button>
</div>
</Transition>
<div/>
);
}
The idea is that in the SaveClient component, when a client is saved, in .then() inside the navigate() function, pass a state in a true condition.
Then in the ClinetList component, I call the state using useLocation(), and I passed in the component {showFlagCreate && <FlagCreateClient />}.
By far this logic works, I create a client, the flag is shown after, but when I reload the page after, the flag is shown. When I make other actions in the webpage which might be clicking the edit button and going back to the ClientList component the flag won't show, even if I reload/refresh the page.
How can I fix this bug?
I'm doing an ssg website, it doesn't have backend, but it does start to have some features that fetch some data from another server. I checked the SWR and it works, the issue is I need to make a post request on button click, and it gets me an error:
hooks can only be called inside of the body of a function component
What I see is that I can create a function component, set up a call in it and just mount this component on button click, it works, but I'm having doubts about this approach.
This is probably done to work with get request, but I make a post.
ExportComponent renders on a page or in another component.
function ExportComponent() {
const [exportCalled, setExportCalled] = useState(false)
const exportCall = () => {
setExportCalled(true)
}
if (exportCalled) {
return (
<CallExport></CallExport>
)
}
return (
<Button
onClick={ exportCall() }
>
Export
</Button>
);
}
function CallExport() {
// api post call
const { data, isLoading } = exportProject();
if (isLoading) {
return <CircularProgress />;
}
return (
// call to api is done, make something with data
<Button>Open</Button>
)
}
export function exportProject() {
const params = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({}),
};
const exportFetcher = (url) => fetch(url, params).then((r) => r.json());
const { data, error } = useSWR('url', exportFetcher);
return {
data,
isLoading: !error && !data,
isError: error
}
}
Is it wrong? Is there a better way?
After doing some reading, I'm pretty sure this warning is being caused by a fetch request I'm making in my component. Whenever I navigate away from said component (using React Navigation), I believe my fetch request results in an unresolved promise, caused by navigating away and thus my fetch returning a promise to an unmounted component.
const MakePicker = (props) => {
const [data, setData] = useState([{text: 'Option 1'}]);
const [selectorState, setSelectorState] = useState([{selectedOption: null}]);
setDataHandler = (data) => {
setData(data);
};
setSelectorStateHandler = (selectedOption) => {
setSelectorState({selectedOption: selectedOption})
};
useEffect(() => {
async function fetchMakeData (makeId) {
let response = await fetch('https://myAPIurlGoesHere',
{
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
let fetchedData = await response.json();
return fetchedData;
}
fetchMakeData().then(fetchedData => {
let data = [];
for (const item of fetchedData.data) {
let dataObj = {
text: item.name,
value: item.name,
id: item.id
};
data.push(dataObj);
}
setDataHandler(data);
})
});
return (
<Layout style={styles.container}>
<Select
style={styles.select}
data={data}
placeholder='Select Make'
selectedOption={selectorState.selectedOption}
onSelect={setSelectorStateHandler}
/>
</Layout>
)
}
My component makes a Fetch request as soon as it's mounted. I did this on purpose, I'd like the items in my 'MakePicker' to be populated before the user even clicks the drop down. My problem is I don't know how to 'cleanup' or cancel subscriptions and asynchronous tasks. I know my fetch should go inside of the useEffect function, as this is what creates a side effect, and I know I'm supposed to return a function to 'cleanup' after. I just don't know what that means, or what it would look like. Any help would be greatly appreciated!!