Confused about using 'useState' in two separate functions - reactjs

In order to get data from my AWS Postgres DB, I have to first get an AWS Access Token and pass it into the GET call. To accomplish this, in my React app, I've created a file called requests.js into which I plan to build a number of functions. Here are the first two:
// Custom hook to get AWS Auth Token
export const useGetAwsAuthToken = () => {
const [data, setData] = useState();
useEffect(() => {
const fetchData = async function() {
try {
const config = {
headers: { "Authorization":
await Auth.currentSession()
.then(data => {
return data.getAccessToken().getJwtToken();
})
.catch(error => {
})
}
};
setData(config);
} catch (error) {
throw error;
} finally {
}
};
fetchData();
}, []);
return { data };
};
// Custom hook for performing GET requests
export const useFetch = (url, initialValue) => {
const [data, setData] = useState(initialValue);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async function() {
try {
setLoading(true);
const response = await axios.get(url);
if (response.status === 200) {
setData(response.data);
}
} catch (error) {
throw error;
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { loading, data };
};
I was under the impression that I could use const [data, setData] = useState(); in both of these functions and that they would be independent of each other. However, back where I call the functions, my IDE is telling me that "data has already been declared" with the 2nd call:
const {data} = useGetAwsAuthToken();
const {loading, data} = useFetch('https://jsonplaceholder.typicode.com/posts');
Furthermore, say I comment out the 2nd line of code above and make this call:
const {data2} = useGetAwsAuthToken();
This leaves data2 as undefined. This is also confusing because shouldn't I be able to have any named return value variable in the calling function?

First, the one that is easier to answer for me: const {data2} = useGetAwsAuthToken isn't valid because you're using destructuring and it's expecting the value of data. So what you're telling it is the equivalent of saying const data2 = useGetAwsAuthToken().data2 What you actually want is const { data: data2 } = useGetAwsAuthToken(). This will take the value that is returned (data) and save it to the current scope as data2.
Now for the first issue you brought up. Why are you using react life cycles in what appears to just be a function? You don't need to save things in state when it's just a function that's not returning a React Component.

Related

Get type error cannot destructure property as it is undefined in React

I have a MongoDB collection and inside this collection, there are some documents. In these documents, I stored some IDs of another collection documents. This is an image of this document.
In the frontend, I access this document and get the postId. I tried this way.
const onePostId=posts.postId
console.log(onePostId);
const type=typeof (onePostId);
console.log(type);
This code part gives me this result.
I try to pass this postId to an API const response = await axios.get(`/buyerGetOnePost/${onePostId}`) like this way. But this postId is a string type I think that's why I can't get results from this API. Then I try like this const {onePostId}=posts.postId then I get an error that says "TypeError: Cannot destructure property 'onePostId' of 'posts.postId' as it is undefined". How do I solve this problem?
This is the complete code that I tried.
function PostsLocation() {
const { offerId } = useParams();
console.log(offerId);
const [posts, setPosts] = useState({});
useEffect(()=>{
getOnePost();
}, []);
const getOnePost = async () => {
try {
const response = await axios.get(`/buyerGetOneSellerOffer/${offerId}`)
console.log(response);
const allPost=response.data.oneOffer;
setPosts(allPost);
} catch (error) {
console.error(`Error: ${error}`)
}
}
console.log(posts);
const onePostId=posts.postId
console.log(onePostId);
const type=typeof (onePostId);
console.log(type);
const [offerPosts, setOfferPosts] = useState({});
useEffect(()=>{
getOneOfferPost();
}, []);
useEffect(()=>{
if (offerPosts && offerPosts.location) {
console.log(offerPosts.location);
console.log(offerPosts.location.longitude);
console.log(offerPosts.location.latitude);
}
}, [offerPosts]);
const getOneOfferPost = async () => {
try {
const response = await axios.get(`/buyerGetOnePost/${onePostId}`)
console.log(response);
const allOfferPost=response.data.onePost;
setOfferPosts(allOfferPost);
} catch (error) {
console.error(`Error: ${error}`)
}
}
console.log(offerPosts);
const long = offerPosts?.location?.longitude;
console.log(long);
const lat=offerPosts?.location?.latitude;
console.log(lat);
const location={lat,long};
}
Below image shows the results after console.log(posts).
You are trying to destructure a value without getting the data first. So, you are getting a error.
You are running the below code for posts at starting when it doesn't have any data
const onePostId=posts.postId
and only after the call to
getOnePost();
your data gets filled but there is a time delay which you should always consider with async await syntax and you should first check if posts contain posts.postId with some value or if defined and then do the destructuring. If it doesn't then probably don't use it either wait for it or return loading or return mock value probably.
eg:
if(!posts.postId){
// posts.postId is not set and it doesn't have data
}

useEffect not triggering but the template is being rendered somehow

I am getting too many re-renders while using react-hooks.
I am trying to fetch data from api by using a parameter in URL.
Here's the code:
export default function Details() {
const { title } = useParams();
const [loading, setLoading] = useState(true);
const [details, setDetails] = useState([]);
const [error, setError] = useState("");
function getDetails(keyword) {
if (keyword) {
setLoading(true);
fetch(
`API`
)
.then((res) => {
let result = res.data.results;
result = result.filter(function (result) {
return (result.title = keyword);
});
setDetails(result[0]);
setLoading(false);
console.log(details);
})
.catch((err) => {
setError(["Unable to fetch data"]);
setLoading(false);
});
}
}
getDetails(title)
return(
// template
)
now I think this is happening at the line where I call getDetails.
then I tried using useEffect to load the data only once after it is mounted,
useEffect(() => {
getDetails(title);
}, []);
It still is unable to fetch the data, as the getDetails function is never called.
How can I resolve this?
Edit:
Fixed one silly error.
Here's the codesandbox link:
Codesandbox
There are multiple issues with this, first you need to specify what you want to be notified about when the useEffect gets called again. You could do this by adding the variables you want within the array
useEffect(() => {
getDetails(title);
}, [
// Add what you want here
]);
The second issue you have is that you declared the detalis variable twice. Once using the set state here: const [details, setDetails] = useState([]);
The second time here:
const details = getDetails(title)
the code here has two obvious error beside the functionality problems you mentioned:
1 - you cannot declare two variables with same name using let or const; it will throw a SyntaxError
const [details, setDetails] = useState([]);
...
const details = getDetails(title)
2- getDetails function is written with a asynchronous mindset, and it will return nothing,
so details in const details = getDetails(title) will be set to undefined
Looks like your getDetails function has title param so I would add title and getDetails both in the dependency list of useEffects
Or place getDetails inside the useEffect
Here is your working code. You had multiple problems where res.data was undefined so you need to get res.results directly based on your response object
useEffect(() => {
function getDetails(keyword) {
if (keyword) {
setLoading(true);
fetch(
`https://api.jikan.moe/v3/search/anime?q=${keyword}&page=1&genre_exclude=0`
)
.then((res) => res.json())
.then((res) => {
console.log(res.results);
let result = res.results;
console.log(result);
result = result.filter(function (result) {
return (result.title = keyword);
});
setDetails(result[0]);
setLoading(false);
console.log(3);
})
.catch((err) => {
console.log(err);
setError(["Unable to fetch data"]);
setLoading(false);
});
}
}
console.log('calling getDetails')
getDetails(title);
}, [title]);
Note: tested in the code sandbox link provided in the question. Its working code.

How to ensure API data is called and added with .then

I am getting data in useEffect and looping through it to add additional data.
I want to do some calculations on it after all data being added to results, I make the calculations inside if (response.data.next) but after that inside then when I try to access data it prints old data.
How can I make make sure all data added then be able to use it in then?
const [results, setResults] = useState([]);
useEffect(() => {
async function handleAPIRequest(url) {
return await axios
.get(url)
.then(async (response) => {
await setResults((results) => [...results, ...response.data.results]);
if (response.data.next) {
await handleAPIRequest(response.data.next);
}
return results;
})
.then(async () => {
// this is where I want to use results
console.log("resultss: ", results);
});
}
handleAPIRequest(url)
}, []);
I would suggest to keep consistency between async/await or chaining promises. your approach can cause multiple setResults, not sure if that's what you desire. Below, I offer a solution that might suit your needs:
useEffect(() => {
async function handleAPIRequest(url, currentResults = []) {
try {
const response = await axios.get(url)
const nextResults = [...currentResults, ...response.data.results];
if (response.data.next) {
return await handleAPIRequest(response.data.next, nextResults);
}
return nextResults;
} catch (error) {
throw error;
}
}
try {
const finalResults = await handleAPIRequest(url);
setResults(results => [...results, ...finalResults]);
} catch (error) {
// here you can handle error response
console.log(error);
}
}, []);
// to do something after results state is updated use another use effect to accomplish that
useEffect(() => {
// do something on updated results state
}, [JSON.stringify(results)]);
You should change your code in the following way:
const [results, setResults] = useState([]);
useEffect(() => {
const handleAPIRequest = async url => {
const lastResult = await axios.get(url);
setResults([...results, ...lastResult.data.results]);
const toLogResults = [...results, ...lastResult.data.results];
if(response.data.next) {
await handleAPIRequest(response.data.next);
} else {
console.log("The final results are", toLogResults);
}
}
handleAPIRequest(url)
}, []);
The main problem is that results in lines following setResults() are not updated immediately, so the value is the old one.
The toLogResults is used just to display the actual result, it is not needed.

Custom react hook causes infinite loop

I have a custom fetch hook:
export const useFetch = (url: string) => {
const [response, setResponse] = useState<any>(null);
const [error, setError] = useState<any>(null);
const fetchList = (url: string) => {
return API.get(AMPLIFY_ENPOINTS.default, url, { response: true });
};
useEffect(() => {
const fetchFunc = async () => {
try {
const fetchResponse = await fetchList(url);
setResponse(fetchResponse.data);
} catch (error) {
setError(error);
}
};
fetchFunc();
}, [url]);
return { response, error };
};
This I then use in a component:
const fetchOrders = useFetch(apiUrl);
useEffect(() => {
const { response, error } = fetchOrders;
if (error) setError(error);
if (response) {...}
}, [fetchOrders]);
And this causes an infinite loop, how should I go about fixing it?
The fetchOrder reference keeps changes on each re-render since you are returning a newly created object each time and hence it triggers an infinite loop when you call setError within your useEffect.
Instead of adding fetchOrders as a dependency, add response and error separately
const { response, error } = useFetch(apiUrl);
useEffect(() => {
if (error) setError(error);
if (response) {...}
}, [response, error]);
It is this line that is responsible for this
return { response, error };
And of course his partner in crime is this [fetchOrders] dependency array in the second useEffect.
The first line returns a new object and thus fetchOrders is always a new value.
After thinking about your code, I came up to conclude that you probably don't need the second useEffect at all (I think).
// the useFetch only re-fetches when url changes
const { response, error } = useFetch(url);
// use response directly, you don't need to re-set the error nor the response in your component.
But if you were to do a side effect with response, you can consider upgrading your useFetch hook to accept a callback parameter and useEffect with a response dependency.
In your component, try wrapping the input in useMemo.
const url = useMemo(() => apiUrl, [])
const fetchOrders = useFetch(url);
useEffect(() => {
const { response, error } = fetchOrders;
if (error) setError(error);
if (response) {...}
}, [fetchOrders]);
Otherwise, apiUrl is a local variable and gets recreated every time the functional component runs, causing the infinite loop.

React - How do I get fetched data outside of an async function?

I'm trying to get the data of "body" outside of the fetchUserData() function.
I just want to store it in an variable for later use.
Also tried modifying state, but didn't work either.
Thanks for your help :)
const [userData, setUserData] = useState();
async function fetchUserData () {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
});
const body = await result.json();
//setUserData(body);
return(
body
)
} catch (err) {
console.log(err);
}
}
let userTestData
fetchUserData().then(data => {userTestData = data});
console.log(userTestData);
//console.log(userData);
Use useEffect
async function fetchUserData () {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
})
return await result.json()
} catch (err) {
console.log(err)
return null
}
}
const FunctionalComponent = () => {
const [userData, setUserData] = useState()
useEffect(() => {
fetchUserData().then(data => {
data && setUserData(data)
})
}, []) // componentDidMount
return <div />
}
Ben Awad's awesome tutorial
Example:
it seems that you are making it more complicated than it should be. When you get the response i.e the resolved promise with the data inside the async function, just set the state and in the next render you should get the updated data.
Example:
const [userData, setUserData] = useState();
useEffect(() => {
const getResponse = async () => {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
});
const body = await result.json();
setUserData(body);
} catch (err) {
console.log(err)
}
}
getResponse();
}, [])
console.log(userData);
return <div></div>
Assuming the you need to call the function only once define and call it inside a useEffect or 'componentDidMount'. For using async function inside useEffect we need to define another function and then call it.
When you do
let userTestData
// This line does not wait and next line is executed immediately before userTestData is set
fetchUserData().then(data => {userTestData = data});
console.log(userTestData);
// Try changing to
async someAsyncScope() {
const userTestData = await fetchUserData();
console.log(userTestData)
}
Example:
state = {
someKey: 'someInitialValue'
};
async myAsyncMethod() {
const myAsyncValue = await anotherAsyncMethod();
this.setState({ someKey: myAsyncValue });
}
/*
* Then in the template or where ever, use a state variable which you update when
* the promise resolves. When a state value is used, once the state is updated,
* it triggers as a re-render
*/
render() {
return <div>{this.state.someKey}</div>;
}
In your example you'd use setUserData instead of this.setState and userData instead of {this.state.someKey}

Resources