Solution to breakout a Promise outside a component - reactjs

Origin code
const getData = useCallback(async () => {
dispatchAnimation(actionSetAnimationState(true));
try {
await Promise.all([getGroupData(), getPolicyData()]);
} catch (err) {
notifyError('error...');
}
dispatchAnimation(actionSetAnimationState(false));
}, [dispatchAnimation, getGroupData, getPolicyData, localize]);
useEffect(() => {
getData();
}, [getData]);
I want to moving the try catch to another file
import { useCallback } from 'react';
import { actionSetAnimationState } from '../reducer/animation/animationAction';
import { useAnimation } from '../reducer/useStore';
import doSleep from './doSleep';
import { notifyError } from './Toast';
async function CustomCallbackWithAnimation(argv: Array<Promise<void>>): Promise<void> {
const { dispatchAnimation } = useAnimation();
dispatchAnimation(actionSetAnimationState(true));
useCallback(async () => {
try {
await Promise.all(argv);
} catch (err) {
notifyError('error');
}
}, [argv]);
dispatchAnimation(actionSetAnimationState(false));
}
export default CustomCallbackWithAnimation;
In .tsx file that need to getData just calling
useEffect(() => {
CustomCallbackWithAnimation([getGroupData(), getPolicyData()]);
}, [edit]);
But I am receiving this error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component
Do you have any solution that better to split this function(meaning I want to reused it any where to avoid duplicate code)?
I want to reused it any where to avoid duplicate code

Instead directly call CustomCallbackWithAnimation that runs hooks inside, make it return callback that you can invoke where you want later.
Something like that
export function useCustomCallback() {
const { dispatchAnimation } = useAnimation();
return useCallback(async (argv: Array<Promise<void>>) => {
dispatchAnimation(actionSetAnimationState(true));
try {
await Promise.all(argv);
} catch (err) {
notifyError('error');
}
dispatchAnimation(actionSetAnimationState(false));
}, [argv])
}
const App = () => {
const callback = useCustomCallback()
useEffect(() => {
callback([firstPromise, secondPromise])
}, [])
return <div></div>
}

Related

React - useEffect based on another useEffect

I'm trying to understand how useEffect works.
I have two callApi: "callApiDialer" is based on response of "callApiManager", for get id from list.
But "currentLeadId" state at first called obviously is null.
How can call "callApiDialer" when currentLeadId is not null?
import React, { useState, useEffect } from 'react';
const [loading, setLoading] = useState(true);
const [apiManager, setApiManager] = useState([]);
const [apiDialer, setApiDialer] = useState([]);
const [currentLeadId, setCurrentLeadId] = useState(null);
// CALL API
const callApiManager = async () => {
try {
const response = await api.get(`/api/manager/op/1`);
setCurrentLeadId(response.data.dialer_list[0].id);
setApiManager(response.data);
} catch (err) {
alert("fetchApiManager " + err.response.status);
}
}
const callApiDialer = async () => {
try {
const response = await api.get(`/api/manager/lead/${currentLeadId}`);
setApiDialer(response.data.lead);
setLoadingModal(false);
} catch (err) {
alert("fetchApiSources " + err.response.status);
}
}
useEffect(() => {
callApiManager();
}, [])
useEffect(() => {
console.log(currentLeadId); // <-- here get first null and after currentLeadId
if(currentLeadId) {
callApiDialer();
setLoading(false);
}
}, [currentLeadId])
You could have just one function that call both, therefore there would be only one useEffect.
// CALL API
const callBothApisAtOnce= async () => {
try {
const op = await api.get(`/api/manager/op/1`);
const response = await api.get(`/api/manager/lead/${op.data.dialer_list[0].id}`);
// rest of your logic...
} catch (err) {
alert("err" + err);
}
}
useEffect(() => {
callBothApisAtOnce()
}, [])
you can use axios's promise base functionality
axios.get(`/api/manager/op/1`).then(res => {
setCurrentLeadId(response.data.dialer_list[0].id);
setApiManager(response.data);
axios.get(`/api/manager/lead/${response.data.dialer_list[0].id}`).then(res1 =>{
setApiDialer(res1.data.lead);
setLoadingModal(false);
}
}

Сustom data upload hook

I wrote a simple code that requests data from a local json server when the page loads. I have this code repeated in several places and I want to put it in the custom hook.
Tell me how to write and apply a custom hook correctly?
const [data, setData] = useState()
useEffect(() => {
const getPosts = async () => {
try {
setData(await getData('http://localhost:3001/posts'))
} catch (error) {
console.log('ERROR >>', error.message)
}
}
getPosts()
}, [])
I tried to write like this, but it doesn't work:
import { useEffect, useState } from "react"
import { getData } from './../helpers'
const useData = url => {
const [currentData, setCurrentData] = useState()
useEffect(() => {
const getCurrentData = async () => {
try {
setCurrentData(await getData(url))
} catch (error) {
console.log('ERROR >>', error.message)
}
}
getCurrentData()
}, [url])
return currentData
}
export default useData
Please check this sandbox, this seems to load data once every page loads. I just don't pass the URL inside useEffect because I only want to call it once on page load.
https://codesandbox.io/s/dank-sky-6fs4rq?file=/src/useData.jsx

Infinite loop when setting and using state in a `useCallback` that is being called from a `useEffect`

I would like to fetch data when the user changes.
To do this I have a useEffect that triggers when the user changes, which calls a function to get the data.
The problem is that the useEffect is called too often because it has a dependency on getData and getData changes because it both uses and sets loading.
Are there ways around this, while still retaining getData as a function, as I call it elsewhere.
const getData = useCallback(async () => {
if (!loading) {
try {
setLoading(true);
const { error, data } = await getDataHook();
if (error) {
throw new Error("blah!");
}
} catch (error) {
const message = getErrorMessage(error);
setErrorMessage(message);
setLoading(false);
}
}
}, [loading]);
...
useEffect(() => {
const callGetData = async () => {
await getData();
};
callGetData();
}, [user, getData]);
Try moving loading from useCallback to useEffect. Something like this:
const getData = useCallback(async () => {
try {
const { error, data } = await getDataHook();
if (error) {
throw new Error("blah!");
}
} catch (error) {
const message = getErrorMessage(error);
setErrorMessage(message);
}
}, []);
...
useEffect(() => {
const callGetData = async () => {
await getData();
};
if (!loading) {
setLoading(true);
callGetData();
setLoading(false);
}
}, [user, getData, loading]);
The loading flag is something that the call sets, and shouldn't be effected by it, so remove it from the useEffect(), and getData() functions.
const getData = useCallback(async () => {
try {
setLoading(true);
const { error, data } = await getDataHook();
if (error) {
throw new Error("blah!");
}
} catch (error) {
const message = getErrorMessage(error);
setErrorMessage(message);
} finally {
setLoading(false); // not related, but this would remove loading after an error as well
}
}, []);
useEffect(() => {
const callGetData = async () => {
await getData(user);
};
callGetData();
}, [user, getData]);

Combine two Axios calls inside one useEffect with async/await

I'm currently trying to understand how to work with async/await in React JS. In this demo app, I'm trying to get full border names of the chosen country by calling https://restcountries.eu/. I make first API call to get info about country and the second one to get full name of its borders since first API call returns
only short border names. I believe there is a way to combine those calls inside one useEffect however everything I tried gave me some sort of an error or getting stuck in infinite loop. What is the proper way to combine those calls with async/await approach?
import React, { useState, useEffect } from "react";
import Axios from "axios";
const App = () => {
const [loading, setLoading] = useState(true);
const [country, setCountry] = useState({});
const [fullBorderNames, setFullBorderNames] = useState([]);
//FIRST API CALL
useEffect(() => {
const source = Axios.CancelToken.source();
const fetchData = async () => {
setLoading(true);
try {
const response = await Axios(
`https://restcountries.eu/rest/v2/name/canada?fullText=true`,
{ cancelToken: source.token }
);
setCountry(response.data[0]);
} catch (err) {
if (Axios.isCancel(err)) {
console.log("Axios canceled");
} else {
console.log(err);
}
}
};
fetchData();
return () => source.cancel();
}, []);
//SECOND API CALL
useEffect(() => {
const source = Axios.CancelToken.source();
let borders = [];
if (country.borders) {
const fetchData = async () => {
try {
country.borders.forEach(async border => {
const response = await Axios(
`https://restcountries.eu/rest/v2/alpha?codes=${border}`,
{ cancelToken: source.token }
);
borders.push(response.data[0].name);
if (borders.length === country.borders.length)
setFullBorderNames(borders);
});
} catch (err) {
if (Axios.isCancel(err)) {
console.log("Axios canceled");
} else {
console.log(err);
}
}
setLoading(false);
};
fetchData();
}
return () => source.cancel();
}, [country.borders]);
if (loading) {
return <h2>Loading</h2>;
} else {
return (
<>
<pre>{JSON.stringify(country, null, 2)}</pre>
<pre>{JSON.stringify(fullBorderNames, null, 2)}</pre>
</>
);
}
};
export default App;
You can simply just make the requests right after the first one.
try {
const response = await Axios(`https://restcountries.eu/rest/v2/name/canada?
fullText=true`, { cancelToken: source.token });
const country = response.data[0];
setCountry(country);
/* all the other fetch calls*/
Can you tell me what kind of errors you get because I don't see an issue with doing them in the same useEffect? It just gets a little messy which can be refactored anyway.

React useEffect inside async function

In react navigation (I could do this in App.ts too) I fire off the authentication like so:
export default function Navigation() {
authenticateUser();
...
}
export default function authenticateUser() {
const setLoadingUser = useStore((state) => state.setLoadingUser);
firebase.auth().onAuthStateChanged(async (authenticatedUser) => {
console.log('AuthenticateUser', authenticatedUser);
setLoadingUser(false);
if (authenticatedUser) {
useAuthenticate(authenticatedUser);
} else {
console.log('No user');
setLoadingUser(false);
}
});
...
}
And for the sake of simplicity, I will just print the user for now:
import { useEffect } from 'react';
export const useAuthenticate = (authenticatedUser) => {
useEffect(() => {
console.log('authenticatedUser', authenticatedUser);
}, [authenticatedUser]);
return true;
};
I believe that because I'm calling useAuthenticate inside the async firebase onAuthStateChanged function, React is throwing [Unhandled promise rejection: Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:]
How do I handle this?
This should be:
export default function authenticateUser() {
const {setAuthenticated} = useAuthenticate();
const setLoadingUser = useStore((state) => state.setLoadingUser);
firebase.auth().onAuthStateChanged(async (authenticatedUser) => {
console.log('AuthenticateUser', authenticatedUser);
setLoadingUser(false);
if (authenticatedUser) {
setAuthenticated(authenticatedUser);
} else {
console.log('No user');
setLoadingUser(false);
}
});
...
}
import { useEffect, useState } from 'react';
export const useAuthenticate = () => {
const [authenticated, setAuthenticated] = useState(false);
useEffect(() => {
console.log('authenticatedUser', authenticated);
}, [authenticated]);
return {authenticated, setAuthenticated};
};

Resources