When I create an item and click save, I would like when I return to the page it automatically updates with what I just created. So that my useEffect detects the changes that have just arrived at the level of the GET request.
But I tried everything, I tried to put the variable garden in the array of useEffect but it makes an infinite loop of GET request, I also tried to put setGarden it does not make an infinite loop but it does not update automatically, I have to reload the page...
Here is the code :
const [garden, setGarden] = useState([]);
const [plot, setPlot] = useState([]);
const [loading, setLoading] = useState(false);
const navigation = useNavigation();
const gardenData = async () => {
setLoading(true);
const user = await AsyncStorage.getItem('user');
const parsedUserData = JSON.parse(user);
try {
const response = await axios.get(
`http://127.0.0.1/api/garden?user=${parsedUserData.user.id}`,
{
headers: {
Authorization: `Token ${parsedUserData.token}`,
},
},
);
if (response.status === 200) {
navigation.navigate('LogScreen');
setGarden(response.data);
setLoading(false);
try {
const plotResponse = await axios.get(
`http://127.0.0.1/api/plots?garden=${response.data[0].id}`,
{
headers: {
Authorization: `Token ${parsedUserData.token}`,
},
},
);
if (plotResponse.status === 200) {
setPlot(plotResponse.data);
}
} catch (e) {
alert(e);
}
}
} catch (e) {
console.log('Erreur ' + e);
setLoading(false);
}
};
useEffect(() => {
gardenData();
}, []);
Thanks for the help !
You can just simply use an indication to make sure the gardenData function in ran once. Either you can initialize one of your state or even you can use a complete new state This is only run the code once.
const [garden, setGarden] = useState([]);
const [plot, setPlot] = useState([]);
const [loading, setLoading] = useState(false);
const [FirstRun, setFirstRun] = useState(true);
const navigation = useNavigation();
const gardenData = async () => {
if(!FirstRun) return;
setFirstRun(false);
setLoading(true);
const user = await AsyncStorage.getItem('user');
const parsedUserData = JSON.parse(user);
try {
const response = await axios.get(
`http://127.0.0.1/api/garden?user=${parsedUserData.user.id}`,
{
headers: {
Authorization: `Token ${parsedUserData.token}`,
},
},
);
if (response.status === 200) {
navigation.navigate('LogScreen');
setGarden(response.data);
setLoading(false);
try {
const plotResponse = await axios.get(
`http://127.0.0.1/api/plots?garden=${response.data[0].id}`,
{
headers: {
Authorization: `Token ${parsedUserData.token}`,
},
},
);
if (plotResponse.status === 200) {
setPlot(plotResponse.data);
}
} catch (e) {
alert(e);
}
}
} catch (e) {
console.log('Erreur ' + e);
setLoading(false);
}
};
useEffect(() => {
if(FirstRun)
{
gardenData();
}
// your code here
}, [garden]);
Related
So I have a function that fetches from an API until it hits a 200 code.
Something like this:
/* These functions are in a separate file */
const fetchFile = async () => {
try {
const resp = await fetch('someUrl');
return resp.data;
} catch (err) {
if(err.response.code === 404){
await sleep(10); //sleep is a custom fn that awaits for N seconds
return fetchFile();
}else{
return 'Error';
}
}
}
const fetchAll = async (setData, setError) => {
const data = await fetchFile();
if(data === 'Error') {
setError('Sorry, error ocurred');
return;
}
setData(data);
}
/* React component */
const [data, setData] = useState([]);
const [error, setError] = useState('');
<button onClick={fetchAll}>Start fetch</button>
And I need to have another button that let's the user stop the recursing function. I know a common way of doing this is having a flag and check the value every time I call the recursing function, but since this is React, how can I achieve this? Should I have the functions inside the component file? Use a global window variable? Use localstorage? What's the best way?
You can do couple of ways, here is one the way using useRef
/* These functions are in a separate file */
const fetchFile = async (cancellationRef) => {
try {
const resp = await fetch('someUrl');
return resp.data;
} catch (err) {
if(err.response.code === 404){
if(cancellationRef.current !==true){
await sleep(10); //sleep is a custom fn that awaits for N seconds
return fetchFile();
}
else{
return 'Request cancelled';
}
}else{
return 'Error';
}
}
}
const fetchAll = async (setData, setError,cancellationRef) => {
const data = await fetchFile(cancellationRef);
if(data === 'Error') {
setError('Sorry, error ocurred');
return;
}
setData(data);
}
/* React component */
const [data, setData] = useState([]);
const cancellationRef = useRef(false);
const [error, setError] = useState('');
<button onClick={fetchAll}>Start fetch</button>
<button onClick={()=>cancellationRef.current =true)}>Stop looping</button>
Here is a full example - I recommend encapsulating the fetching behavior in a hook like this. An AbortController is used for cancelling any ongoing request. This takes advantage of useEffect and its cleanup function, which is described in the react docs:
If your effect returns a function, React will run it when it is time to clean up:
/** Continuously makes requests until a non-404 response is received */
const fetchFile = async (url, signal) => {
try {
const resp = await fetch(url, { signal });
return resp.data;
} catch (err) {
if(err.response.code === 404){
await sleep(10);
return fetchFile(url, signal);
}
// there was an actual error - rethrow
throw err;
}
}
/** Hook for fetching data and cancelling the request */
const useDataFetcher = (isFetching) => {
// I advise using `null` as default values
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
if (isFetching) {
const controller = new AbortController();
fetchFile('someUrl', controller.signal)
.then(result => {
setData(result);
setError(null);
})
.catch(err => {
if (err.name === "AbortError") {
// request was aborted, reset state
setData(null);
setError(null);
} else {
setError(err);
}
});
// This cleanup is called every time the hook reruns (any time isFetching changes)
return () => {
if (!controller.signal.aborted) {
controller.abort();
}
}
}
}, [isFetching]);
return { data, error };
}
const MyComponent = () => {
const [isFetching, setIsFetching] = useState(false);
const { data, error } = useDataFetcher(isFetching);
const startFetch = useCallback(() => setIsFetching(true), []);
const cancelFetch = useCallback(() => setIsFetching(false), []);
useEffect(() => {
if (data || error) {
setIsFetching(false);
}
}, [data, error];
return isFetching
? <button onClick={cancelFetch}>Cancel</button>
: <button onClick={startFetch}>Start fetch</button>
}
my custom hooks :
import { useContext, useState } from "react";
import { AppContext } from "../context/app.context";
const usePostAxios = (url) => {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const { setUrl, token } = useContext(AppContext);
const postData = async (data) => {
try {
setIsLoading(true);
axios.defaults.headers.common["X-Authreq"] = token;
await axios
.post(`${setUrl}/${url}`, data)
.then(({ data }) => {
setData(data);
})
.catch((err) => {
setError(err.response || err.request);
});
setIsLoading(false);
} catch (err) {
console.log(err);
}
};
return [isLoading, postData, data, error];
};
export { usePostAxios };
for posting data :
const [isPosting, postFun, res, err] = usePostAxios("/apidet/EmpLog");
const handleSubmit = async (val) => {
await postFun({
UserName: val.email,
password: val.pass,
});
if (res) {
const { Apd, Apt } = res.ResponseData;
dispatch({ type: "APP-LOGIN", token: `${Apd}:${Apt}` });
setIsError(false);
navigate("Home");
return;
} else {
setIsError(true);
return;
}
};
then why my code is getting in else block and getting error but when i press submit button again the code run successfully and my login process success
so why is my code not running on first try ?
am i doing something stupid ?
Well, wrote in this way, basically, when this this code is excecuted
if(res) {
// Something to do with result
}
the variable res has null as value, initially.
You should check for changes in res with useEffect instead, like:
useEffect(()=> {
if(res) {
// Something to do with result
}
}, [res])
I have a section on a webpage for a task :
In the form i write an email which is 'validated' later with a function.First when i submit an email which passed the validation it sends a sendSubscribe function to the server,after that i click the button unsubscribe and it sends a unsubscribeUser function.But,after that,when i click on the email input,it starts to send unsubscribe fetch requests everytime and when i click on the subscribe button it also does the same.
The network tab looks like this:
I think i know which is the problem,but i dont know how to fix it.My idea is that everytime i click on the subscribe button it ataches an event listener from the function,thats why it fires multiple unsubscribe requests.
Subscribe functions : the subscribeEmail is the most important
import { validateEmail } from './email-validator.js'
import { unsubscribeUser } from './unsubscribeFetch.js'
export const subscribe = () => {
const subscribeBtn = document.getElementById('subscribeButton')
subscribeBtn.setAttribute('value', 'Unsubscribe')
document.getElementById('emailForm').style.display = 'none'
localStorage.setItem('isSubscribed', 'true')
document.getElementById('submit-info').value = ''
}
export const unsubscribe = () => {
const subscribeBtn = document.getElementById('subscribeButton')
subscribeBtn.setAttribute('value', 'Subscribe')
document.getElementById('emailForm').style.display = 'block'
localStorage.setItem('isSubscribed', 'false')
}
export const subscribeEmail = (email) => {
const isValidEmail = validateEmail(email)
if (isValidEmail === true) {
subscribe()
document.querySelector('form').addEventListener('click', function (e) {
unsubscribe()
unsubscribeUser()
localStorage.removeItem('Email')
e.stopPropagation()
})
} else if (isValidEmail === false) {
unsubscribe()
}
}
Subscribe fetch functions:
import { validateEmail } from './email-validator.js'
export const sendSubscribe = (emailInput) => {
const isValidEmail = validateEmail(emailInput)
if (isValidEmail === true) {
sendData(emailInput)
}
}
export const sendHttpRequest = (method, url, data) => {
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: data
? {
'Content-Type': 'application/json'
}
: {}
}).then(response => {
if (response.status >= 400) {
return response.json().then(errResData => {
const error = new Error('Something went wrong!')
error.data = errResData
throw error
})
}
return response.json()
})
}
const sendData = (emailInput) => {
sendHttpRequest('POST', 'http://localhost:8080/subscribe', {
email: emailInput
}).then(responseData => {
return responseData
}).catch(err => {
console.log(err, err.data)
window.alert(err.data.error)
})
}
Unsubscribe fetch function:
export const unsubscribeUser = () => {
fetch('http://localhost:8080/unsubscribe', { method: 'POST' }).then(response => { console.log(response.status) })
}
Subscribe button event listener:
document.querySelector('form').addEventListener('submit', async function (e) {
// create a variable to store localStorage email value
const introducedEmail = inputForm.value
e.preventDefault()
console.log(introducedEmail)
localStorage.setItem('Email', introducedEmail)
subscribeEmail(introducedEmail) //change the button style and set in local storage isSubscribed to true
sendSubscribe(introducedEmail) //send subscribe fetch to the server
// prevent additional requests upon clicking on "Subscribe" and "Unsubscribe".
if (isFetching) return // do nothing if request already made
isFetching = true
disableBtn()
const response = await fetchMock() //eslint-disable-line
isFetching = false
enableBtn()
})
// functions for disabling the submit button when a fetch request is in progress
const fetchMock = () => {
return new Promise(resolve => setTimeout(() => resolve('hello'), 2000))
}
const disableBtn = () => {
submitForm.setAttribute('disabled', 'disabled')
submitForm.style.opacity = '0.5'
}
const enableBtn = () => {
submitForm.removeAttribute('disabled')
submitForm.style.opacity = '1'
}
}
Could you guys please help me? I have no idea how to fix this.Thanks in advance!
I modified all your functions and fixed your problem, implemented async and await.
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
Subscribe fetch functions
import { validateEmail } from './email-validator.js'
export const sendSubscribe = async (emailInput) => {
const isValidEmail = validateEmail(emailInput) // idk if this is async func
if (isValidEmail === true) {
await sendData(emailInput);
}
}
export const sendHttpRequest = async (method, url, data) => {
return await fetch(url, {
method: method,
body: JSON.stringify(data),
headers: data
? {
'Content-Type': 'application/json'
}
: {}
}).then(response => {
if (response.status >= 400) {
return response.json().then(errResData => {
const error = new Error('Something went wrong!')
error.data = errResData
throw error
})
}
return response.json()
})
}
const sendData = async (emailInput) => {
await sendHttpRequest('POST', 'http://localhost:8080/subscribe', {
email: emailInput
}).then(responseData => {
return responseData
}).catch(err => {
console.log(err, err.data)
window.alert(err.data.error)
})
}
Unsubscribe fetch function
export const unsubscribeUser = async () => {
await fetch('http://localhost:8080/unsubscribe', { method: 'POST' }).then(response => { console.log(response.status) })
}
Subscribe button event listener
let isFetching = false;
document.querySelector('form').addEventListener('submit', async function (e) {
e.preventDefault();
if (isFetching) return // do nothing if request already made
// create a variable to store localStorage email value
const introducedEmail = inputForm.value;
console.log(introducedEmail);
localStorage.setItem('Email', introducedEmail);
// prevent additional requests upon clicking on "Subscribe" and "Unsubscribe".
disableBtn();
await fetchMock();
isFetching = true;
await subscribeEmail(introducedEmail); //change the button style and set in local storage isSubscribed to true
await sendSubscribe(introducedEmail); //send subscribe fetch to the server
// data sent, reenabling button
isFetching = true
enableBtn();
});
// functions for disabling the submit button when a fetch request is in progress
...
const fetchMock = () => {
return new Promise(resolve => setTimeout(() => resolve('hello'), 2000))
}
const disableBtn = () => {
submitForm.setAttribute('disabled', 'disabled')
submitForm.style.opacity = '0.5'
}
const enableBtn = () => {
submitForm.removeAttribute('disabled')
submitForm.style.opacity = '1'
}
}
So,like i said,the problem was in the function that attached a new event listener to the button,thats why unsubscribe request was sent everytime with +1 more request. So,i did this way :
Function for the submit button:
let isUsed = false
const submitClickButton = async () => {
// create a variable to store localStorage email value
const introducedEmail = inputForm.value
//e.preventDefault()
console.log(introducedEmail)
localStorage.setItem('Email', introducedEmail)
subscribeEmail(introducedEmail) //change the button style and set in local storage isSubscribed to true
sendSubscribe(introducedEmail) //send subscribe fetch to the server
// prevent additional requests upon clicking on "Subscribe" and "Unsubscribe".
if (isFetching) return // do nothing if request already made
isFetching = true
disableBtn()
const response = await fetchMock() //eslint-disable-line
isFetching = false
enableBtn()
isUsed = true
}
const undoClickButton = () => {
//e.preventDefault()
//unsubscribeEmail()
unsubscribeEmail()
isUsed = false
}
const toggleButton = () => {
isUsed ? undoClickButton() : submitClickButton()
}
submitForm.addEventListener('click', toggleButton, false)
And subscribeEmail function :
export const subscribeEmail = (email) => {
const isValidEmail = validateEmail(email)
if (isValidEmail === true) {
subscribe()
// document.querySelector('form').addEventListener('click', function (e) {
// unsubscribe()
// unsubscribeUser()
// localStorage.removeItem('Email')
// e.stopPropagation()
// })
} else if (isValidEmail === false) {
unsubscribe()
}
}
export const unsubscribeEmail = () => {
// const isValidEmail = validateEmail(email)
// if (isValidEmail===true){
unsubscribe()
unsubscribeUser()
localStorage.removeItem('Email')
//}
}
I'm making a get request to an API in a useEffect(). When I navigate to the page from the homepage it loads fine, but as soon as i refresh the page http://localhost:3000/coins/coin I get a Unhandled Runtime Error: Error: Network Error.
export async function getServerSideProps({ query }) {
const id = query;
return {
props: { data: id },
};
}
function index({ data }) {
const coinURL = data.id; // bitcoin
const apiEndpoint = `https://api.coingecko.com/api/v3/coins/${coinURL}`;
const [currentUser, setCurrentUser] = useState();
const [coinData, setCoinData] = useState([]);
useEffect(() => {
const getData = async () => {
const res = await axios.get(apiEndpoint);
const { data } = res;
setCoinData(data);
};
const getCurrentUser = async () => {
const res = await axios.get(
`http://localhost:5000/api/users/${session?.id}`
);
const { data } = res;
setCurrentUser(data);
};
getData();
getCurrentUser();
}, [coinData, currentUser]);
}
Why does this happen?
I'm recommending to do something like this:
const getData = async () => {
try {
const res = await axios.get(apiEndpoint);
const { data } = res;
setCoinData(data);
} catch(err) {
console.log(err)
}
};
const getCurrentUser = async () => {
try {
const res = await axios.get(
`http://localhost:5000/api/users/${session?.id}`
);
const { data } = res;
setCurrentUser(data);
} catch(err) {
console.log(err)
}
};
useEffect(() => {
getData();
getCurrentUser();
}, [coinData, currentUser]);
if you do so, you will be able to view the exact error and fix it.
What is the proper used of UseEffect in react-Native? this is the error
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
const [task, setTask] = useState(null);
const fetchData = React.useCallback( () => {
const newTask = InteractionManager.runAfterInteractions( async () => {
try {
const _token = await AsyncStorage.getItem('#token');
let UserId = await AsyncStorage.getItem('#UserID')
setToken(_token);
fetchParents(UserId, _token);
fetchStudent(UserId, _token);
fetchTeacher(UserId, _token);
UserProfile(UserId, _token)
} catch(e) {
console.log(e);
}
});
setTask(newTask)
}, [])
useEffect( () => {
fetchData()
// to cleanup tasks on component will mount
return () => {
task.cancel();
setTask(null) // to remove task from state
}, []}
);
const fetchParents = async (UserId, _token) => {
try {
fetch(config.API_URL + '/Parents/Application/Access/10/'+UserId, {
method: 'GET',
headers: {
//Headers
'Authorization': 'Bearer '+ _token,
'Content-Type': 'application/json',
},
})
.then((response) => response.json().then(data=>({status:response.status, body:data})) )
.then((response) => {
if(response.status==200)
{
if(response.body.length > 0)
{
setParent(response.body[0].udf_Users_IsHaveApplicationAccess)
}else{
console.log("err");
}
}else{
console.log("error");
}
});
} catch (e) {
console.log(e);
}
}
const fetchStudent = async (UserId, _token) => {
.....//same code in fetchParents
}
const fetchTeacher = async (UserId, _token) => {
.....//same code in fetchParents
}
const UserProfile = async (UserId, _token) => {
.....//same code in fetchParents
}
Do this
const fetchData = React.useCallback( () => {
const newTask = InteractionManager.runAfterInteractions( async () => {
const _token = await AsyncStorage.getItem('#token');
let UserId = await AsyncStorage.getItem('#UserID')
setToken(_token);
fetchParents(UserId, _token);
fetchStudent(UserId, _token);
fetchTeacher(UserId, _token);
UserProfile(UserId, _token)
});
setTask(newTask)
}, [])
// your useEffect
useEffect(() => {
fetchData()
}, [reRender]);