Preferred way of using asynchronous function in useEffect - reactjs

I know two ways to use asynchronous functions in useEffect. I read somewhere that the first way is wrong. Which way do you think is better?
first method
async function fetchData() {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=react',
);
setData(result.data);
}
useEffect(() => {
fetchData();
}, [miliko]);
second method
useEffect(() => {
async function fetchData() {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=react',
);
setData(result.data);
}
fetchData();
}, [miliko]);

Both solutions are correct and will work as long as the data that fetchData uses is within its lexical scope
The only difference in the two approaches is that a new reference for fetchData will be created on every render in the first case, whereas in the second case a new reference will be create only on initial render and when miliko changes
To keep the relevant code together, you can go ahead with the second approach which will it easier for you to cancel the previous request if a new request is made so that you don't see inconsistencies
useEffect(() => {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
async function fetchData() {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=react', {
cancelToken: source.token
}
);
setData(result.data);
}
fetchData();
return () => {
source.cancel('Effect cleared');
}
}, [miliko]);

Related

How do I properly set up my useEffect so I don't receive a missing dependency warning?

I am receiving this warning "React Hook React.useEffect has missing dependencies: 'fetchData' and 'source'. Either include them or remove the dependency array react-hooks/exhaustive-deps". This is my function:
function EmployeesPage(props: any) {
const companyId = props.match.params.id;
const source = axios.CancelToken.source();
const fetchData = async () => {
try {
const response = await axios.get<IEmployees[]>(`${process.env.PUBLIC_URL}/api/company/${companyId}/employees`, {
cancelToken: source.token
});
setEmployees(response.data);
setLoading(true);
} catch (error) {
if (axios.isCancel(error)) {
} else {
throw error;
}
}
}
const deleteEmployee = async (EmployeeId: any) => {
const response = await axios.delete(`${process.env.PUBLIC_URL}/api/company/${companyId}/employees/${employeeId}`);
if (response) await fetchData();
}
React.useEffect(() => {
fetchData()
return () => {
source.cancel();
};
}, [])
I tried to fix this by putting fetchData inside of the useEffect and moving the deleteEmployee out, but this causes my endpoint to be called in an infinite loop. Then I tried the useCallback function and also created an infinite loop.
const fetchData = useCallback(async () => {
try {
const response = await axios.get<IEmployees[]>(`${process.env.PUBLIC_URL}/api/company/${companyId}/employees`, {
cancelToken: source.token
});
setEmployees(response.data);
setLoading(true);
} catch (error) {
if (axios.isCancel(error)) {
} else {
throw error;
}
}
}, [source, CompanyId]);
React.useEffect(() => {
fetchData()
return () => {
source.cancel();
};
}, [fetchData, source])
const deleteEmployee = async (EmployeeId: any) => {
await axios.delete(`${process.env.PUBLIC_URL}/api/company/${companyId}/employees/${employeeId}`);
}
It is my understanding that the only thing that should be going in the dependency array would be something that is going to change. I think my dependency array should be empty because I don't want anything to change. It is going to be the same data being returned each time unless a new employee is added. I'm not sure how to fix this to get the warning message to go away. I have see that there is a way to disable the warning but I am not sure I should do that.
The effect runs in an infinite loop since the source object changes in every render. Move it inside the effect. And move the fetchData function inside the effect as well since it needs access to source.
You should add companyId to the dependencies array to make sure the data is refetched when companyId changes. The setEmployees and setLoading references don't change so it is safe to add them - they won't cause the effects to re-run.
React.useEffect(() => {
const source = axios.CancelToken.source()
const fetchData = async () => {
//...
}
fetchData()
return () => {
source.cancel()
}
}, [companyId, setEmployees, setLoading])
I would recommend reading this to understand if it is safe to omit functions from the dependencies array.
You could declare both fetchData and source inside the useEffect, since it does not use anything besides setState functions. This way, fetchData won't be declared over and over on each re-render.
useEffect(() => {
const source = axios.CancelToken.source();
const fetchData = async () => {
...
};
fetchData();
return () => {
source.cancel();
};
}, [setEmployee, setLoading]);

returning promise from useEffect

I was going through the article https://www.robinwieruch.de/react-hooks-fetch-data.
It gives the below 2 snippets to demonstrate how to deal with promises in useEffect. The first one throws an error while to second one doesnt.
first snippet -
useEffect(async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
}, []);
second snippet -
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
};
fetchData();
}, []);
Why does the second not throw an error, when fetchdata() is called it will return a promise, and thus a promise will be returned from useEffect too. How is the second snippet different from the first one ?
You can only return nothing or a cleanup function for useEffect hook.
The reason the first snippet is throwing an error is because you marked the function async and the functions that are marked async will always return a promise.
//The function is marked async
useEffect(async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
}, []);
It breaks the rule of returning either nothing or a cleanup function.
However In the second snippet, you are using useEffect function to call an async function, but since the useEffect function itself is not async, that means it is not returning a promise
//this is not marked async
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
};
fetchData();
}, []);
From what l know you cant use promise directly on React Hook. You can use it inside in function and it will work. Hooks are there so that you do not need to use classes in React.
More about Hooks => https://reactjs.org/docs/hooks-overview.html

How does this code work in this specifics regarding async wait

I learn some React and Redux and have a beginner question.
In the GitHub code below there are two method calls getInitUsers() and getMoreUsers().
Here is Original GitHub code for the below code
....
useEffect(() => {
const getUsers = async () => {
await getInitUsers();
}
getUsers();
}, [getInitUsers]);
const addMoreUsers = () => {
if(!isGettingMoreUsers) {
getMoreUsers(prevDoc);
}
}
....
const mapDispatchToProps = dispatch => ({
getInitUsers: () => dispatch(asyncGetInitUsers()),
getMoreUsers: prevDoc => dispatch(asyncGetMoreUsers(prevDoc))
})
...
The Redux action for the above getInitUsers() and getMoreUsers() are this two:
Here is the original GitHub code for the below code
export const asyncGetInitUsers = () => {
return async dispatch => {
try {
dispatch(getUsersStart());
const usersRef = firestore.collection("users").orderBy("registrationDate", "desc").limit(30);
usersRef.onSnapshot(docSnapShot => {
let users = [];
docSnapShot.docs.forEach(doc => {
users.push({id: doc.id, data: doc.data()});
});
dispatch(getUsersSuccess(users));
const lastDoc = docSnapShot.docs[docSnapShot.docs.length - 1];
dispatch(setPrevDoc(lastDoc));
});
} catch (errorMsg) {
dispatch(getUsersFailure(errorMsg));
}
}
}
export const asyncGetMoreUsers = prevDoc => {
return async dispatch => {
try {
dispatch(getMoreUsersStart());
const usersRef = firestore.collection("users").orderBy("registrationDate", "desc").startAfter(prevDoc).limit(30);
usersRef.onSnapshot(docSnapShot => {
let users = []
docSnapShot.docs.forEach(doc =>{
users.push({id: doc.id, data: doc.data()})
});
dispatch(getMoreUsersSuccess(users));
const lastDoc = docSnapShot.docs[docSnapShot.docs.length - 1];
dispatch(setPrevDoc(lastDoc));
});
} catch (e) {
dispatch(getMoreUsersFailure(e));
}
}
}
I understand placing the getInitUsers() in the useEffect() will make it run once on Component creation. What I want to ask is what does this await do on this line:
await getInitUsers();
If you look at the getMoreUsers() it does not have the await and if you look at the two action asyncGetInitUsers() and asyncGetMoreUsers() abowe they have the same logic and starts with:
return async dispatch => {...
So what is the difference here? getInitUsers() and getMoreUsers()??
I created a CodeSandbox to try to understand the await
In this case, the await does nothing different, there's actually no point in using async await because you aren't doing anything after the await or returning a value from it.
So, the getInitUsers bit could be simplified to:
useEffect(() => {
getInitUsers();
}, [getInitUsers]);
For example, if you wanted to run some code after the getInitUsers finished. For example, a loading boolean:
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const getUsers = async () => {
await getInitUsers();
setLoading(false);
};
getUsers();
}, [getInitUsers]);
Though due to the simplicity of the code, this could be simplified by using promises directly:
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
getInitUsers().then(() => {
setLoading(false);
});
}, [getInitUsers]);
For some documentation on async await and what it does for us: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await, but here's a bit of an intro:
Essentially, async/await is a combination of features that allow for easier asynchronous flows in JS.
These features basically act as syntactic sugar on top of promises,
making asynchronous code easier to write and to read afterwards. They
make async code look more like old-school synchronous code, so they're
well worth learning. This article gives you what you need to know.
async functions always return a promise, and they do so the moment they are called.
When the async function finishes execution, then that promise resolves to the value returned from it (undefined if nothing is returned) or is rejected with an error.
await only works in async functions (though there is a proposal that's in stage 3 for Top Level Await). await takes a promise and waits for it to be resolved or rejected before continuing execution and unwrapping the promise.
So, without async/await, you need to use the .then or .catch functions of promises, but with async/await, you can do a lot to reduce callback hell.
Here's some very contrived examples to show how using the async/await syntatic sugar can make code easier to read and reason through. Though the biggest danger is also that async/await makes the code look synchronous even though it's not.
// Setting up a couple promises
let promise = new Promise(resolve => resolve(42));
let promise2 = new Promise(resolve => resolve(8));
// Using promises to multiply them together
Promise.all([promise, promise2])
.then(([value, value2]) => value * value2)
.then(value => console.log('promises', value))
// Setting up a couple promises
let promise3 = new Promise(resolve => resolve(42));
let promise4 = new Promise(resolve => resolve(8));
// Using async/await to multiply them together
(async() => {
let value = await promise3 * await promise4;
console.log('async/await', value);
})()

async fetch triggered three times

I use this in my react app to fetch data from my backend server:
React.useEffect(() => {
const fetchWidgets = async () => {
const response = await fetch("http://localhost:1202/data");
const responseData = await response.json();
setData(responseData);
console.log(responseData);
};
fetchWidgets();
});
It fetching data works fine, but the function seems to be triggered three times for some reason.
responseData is logged three times.
React.useEffect runs every time after component renders, unless you tell it not by defining a dependency array as its second argument; since you are setting a state inside its body which causes the comonent to re-render, you will see it happens multiple times. to fix the problem you may pass an empty array [] it will only run once after first render and acts like componentDidMount in class components. or add some dependency to run only if the dependencies change;
React.useEffect(() => {
const fetchWidgets = async () => {
const response = await fetch("http://localhost:1202/data");
const responseData = await response.json();
setData(responseData);
console.log(responseData);
};
fetchWidgets();
},[]);
Use Empty Brackets for the second parameter of useEffect.
React.useEffect(() => {
const fetchWidgets = async () => {
const response = await fetch("http://localhost:1202/data");
const responseData = await response.json();
setData(responseData);
console.log(responseData);
};
fetchWidgets();
},[]);
That will ensure the useEffect only runs once.

Unexpected behavior when fetching data with hooks

I'm trying to fetch data with hooks, and I'm getting an unexpected result.
When I try to use an async callback with useEffect, it throws a warning saying it's bad practice, even though the code works (Commented out in the attached example below)
But when I try to declare an async function within useEffect, it doesn't work (example below)
What am I missing?
https://codesandbox.io/s/mystifying-aryabhata-glqwz?fontsize=14
You should put all the effect logic inside the fetchData() and the call it separately within the useEffect:
useEffect(() => {
const fetchData = async () => {
try {
const result = await axios("https://hn.algolia.com/api/v1/search?query=redux");
setData(result.data);
} catch (e) {
console.log(e);
}
}
fetchData();
}, []);
You can fix it like this :
useEffect(() => {
const fetchData = async () => {
try {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
} catch(e) {
console.log(e);
}
}
fetchData();
}, []);
https://codesandbox.io/s/cocky-water-c0w9i
The problem in your code was, in these lines :
const result = fetchData();
setData(result.data);
here fetchData is async and will return a promise not the actual result, and so result.data is undefined
the callback passed to useEffect must return either a cleanup callback or undefined, when you mark a function as async, it returns a promise implicitly
you can create a function inside the useEffect callback that you can mark as async
React.useEffect(() => {
async function fetchData() {
// write your request
}
fetchData();
})
look to this solution in the
simple solution

Resources