I'm some confused about some different ways to use the useEffect hook to fetch API data. I want to know if there is a "best way" to do this, performance related, or if it really doesn't matter how to do it.
Consider the following ways:
Mutiple function calls to fetch API data inside a single useEffect:
useEffect(() => {
const fetchStudents = async () => {
const students = await studentService.getAll()
setStudents(students)
}
const fetchCourses = async () => {
const courses = await courseService.getAll()
setCourses(courses)
}
const fetchSchedules = async () => {
const schedules = await scheduleService.getAll()
setSchedules(schedules)
}
fetchStudents()
fetchCourses()
fetchSchedules()
}, [])
A single function call to fetch all the api data inside a single useEffect:
useEffect(() => {
const fetchAllData = async () => {
const students = await studentService.getAll()
const courses = await courseService.getAll()
const schedules= await scheduleService.getAll()
setStudents(students)
setCourses(courses)
setSchedules(schedules)
}
fetchAllData()
}, [])
Maybe, multiple useEffects for each api call:
useEffect(() => {
const fetchStudents= async () => {
const students = await studentService.getAll()
setStudents(students)
}
fetchStudents()
}, [])
useEffect(() => {
const fetchCourses = async () => {
const courses = await courseService.getAll()
setCourses(courses)
}
fetchCourses()
}, [])
useEffect(() => {
const fetchSchedules = async () => {
const schedules= await scheduleService.getAll()
setSchedules(schedules)
}
fetchSchedules()
}, [])
Is there another way to consider? Let it be known.
In your second example you wait for each promise to resolve before executing the next one, this will hurt performance, the other examples are all running in parallel.
I would go with Promise.all inside a single useEffect because i think its more readable then 3 useEffect or 3 functions, and this will also make sure all of our promises are executing in parallel.
Note that if one of the promises inside Promise.all reject, the function is going to throw and you won't have any access to the resolved promises
useEffect(() => {
Promise.all([
studentService.getAll().then(setStudents),
courseService.getAll().then(setCourses),
scheduleService.getAll().then(schedules)
])
}, [])
Related
I am trying to make two different api calls -- get the array results and then concatenate them together into 1 array, which I then present in the return.
I am having difficulty with :
a) There are two calls so the array is populated 2x or
b) The data is not persisted so I lose it between calls.
The goal is to make 2 calls to the api (each once) and concatenate the arrays together into 1 result -- which is cleared or emptied between page refresh. So it is not called more than once ... or is not continuously lengthened.
My code is as follows:
const [results, setResults] = useState([]);
useEffect(() => {
getStories();
getContributions();
}, []);
const getStories = async () => {
const { data } = await axios.get(`/api/stories/admin`); // get the data
setResults((currentArray) => [...currentArray, ...data]);
};
const getContributions = async () => {
const { data } = await axios.get(`/api/contributions/admin`);
setResults((currentArray) => [...currentArray, ...data]);
};
useEffect(() => {
async function loadBootstrap() {
const bootstrap = await import(
"../../../node_modules/bootstrap/dist/js/bootstrap"
);
var tooltipTriggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="tooltip"]')
);
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
}
loadBootstrap();
}, []);
return (<> {JSON.stringify(results)}
</>);
You should fetch them both and then set the result once.
Something like the following:
useEffect(() => {
getStoriesAndContributions();
}, []);
const getStoriesAndContributions = async () => {
const [{ data: stories }, { data: contributions }] = await Promise.all([
axios.get(`/api/stories/admin`),
axios.get(`/api/contributions/admin`),
]);
setResults([...stories, ...contributions]);
};
The Promise.all() is not really needed, just in order to make it parallel.
I'm not sure I quite get what you mean when listing the problems, but why not simply do:
const loadArrayElements = async () => {
const { data: stories } = await axios.get(`/api/stories/admin`);
const { data: contributions } = await axios.get(`/api/contributions/admin`);
setState([...stories, ...contributions]);
}
useEffect(() => void loadArrayElements(), []);
What you can do is fetch and store the two data sources in javascript variables and call the setState one time in the useEffect :
useEffect( async () => {
fetchData()
}, []);
const fetchData = async () => {
const { data:stories } = await axios.get(`/api/stories/admin`);
const { data:contributions } = await axios.get(`/api/contributions/admin`);
setResults([...stories, ...contributions])
}
I'm chaining two fetch calls to retrieve data. The first call gets a token and then second call uses that token to get the data. Here's an example:
fetch("[GET THE TOKEN]")
.then((response) => response.json())
.then(token => {
fetch("[GET DATA]?token="+token)
.then((response) => response.json())
.then((data) => {
return data;
});
})
The issue is that I need to make lots of different calls sometimes within the same component and writing that chained call over and over again can get tedious and if I need to make changes it's a lot of code to edit.
I came up with a functional solution but I haven't stress tested it yet. I'm still a react noob so feedback would be helpful
context.jsx
const [token,setToken] = useState('')
const fetchToken = async () => {
fetch("[GET THE TOKEN]")
.then(response => response.json())
.then(data => {
setToken(data);
});
}
component.jsx
const {token, fetchToken } = useContext(context)
//fetch data function
const [initFetch,setInitFetch] = useState(false);
const fetchData = () => {
fetch("[GET DATA]?token="+token)
.then((response) => response.json())
.then((data) => {
return data;
});
}
//action that inits fetch data
const submitForm = () => {
setInitFetch(true)
}
//useEffect that will trigger the function on initFetch and token const changing values
useEffect(() => {
if (token && initFetch === true) {
fetchData();
}
},[token,initFetch]);
I see that you want to call this fetch function when you submit the form, Therefore, you should not do that with an useEffect, simply because you don't need to.
Use the useCallback hook and create an async function inside of it. Call it almost wherever you want.
See:
const MyComponent = () => {
const {token, fetchToken } = useContext(context)
const [initFetch, setInitFetch] = useState(false);
const fetchData = useCallback(async () => {
const response1 = await fetch(`[GET DATA]?token=${token}`);
const jsonFromResponse1 = await response1.json();
return jsonFromResponse1;
}, [token])
const randomFunc = async () => {
const data = await fetchData()
}
return (
<button onClick={fetchData}>Button</button>
)
}
The dependency array of useCallback is crucial, if you don't insert it, when the token changes, the hook will never update the function with its new value.
You can continue to use then. But I strongly recommend you to try await/async. It will make your code way easier and readable.
What I get from you question, that you are looking for some way to not repeat your fetch calls.
If that's so, I believe you can go with a custom hook that you can call every time you need.
something like that
const useFetchFn=(arg)=>{
fetch(arg)
.then((response) => response.json())
.then((data) => {
return data;
});
}
Then in your component
const [token,setToken] = useState('')
const fetchToken =useFetchFn("[GET THE TOKEN]")
I am trying to use Express (axios) to build a React app.
I was able to get an array of objects from MongoDB using get() method. Currently the list is printed out to the console. How can I assign it to a variable so that I could use that array for further actions?
useEffect(() => {
const expensesListResp = async () => {
await axios.get('http://localhost:4000/app/expenseslist')
.then(
response => console.log(response.data))
}
expensesListResp();
}, []);
Many thanks!
You can assign it in the following way, let say you have an array posts:
const [posts, setPosts] = useState([]);
useEffect(() => {
axios.get('url')
.then(res => setPosts(res.data))
.catch(err => console.log(err));
}, [])
In your code, you can do it in this way:
const [resultArray, setResultArray] = useState([]);
useEffect(() => {
const expensesListResp = async () => {
await axios.get('http://localhost:4000/app/expenseslist')
.then(
response => setResultArray(response.data))
}
expensesListResp();
}, []);
I am assuming that you have data printed on the console.log(response.data) and you want it to be assigned to a variable so that you can use it right?
if that's the case you are already using async function just name it with whatever variable name you want it to be before await.
for example:
const expensesListResp = async () => {
const "your variable name" = await axios.get('http://localhost:4000/app/expenseslist')
}
you can also save that variable in your state, if you want to use that variable data throughout your application.
I am using a useEffect to get information from firebase and set my redux state thereafter. When I open the page for the first time, all my components contain the correct information. As soon as I refresh the page, all the information is set to nothing? I think it is because the useEffect does not execute again for some reason. Here is my code below:
useEffect(async () => {
setLoading(true);
const fetchData = async () => {
await getConfigs().then((response) => {
const obj = response;
setRedux(obj[0]);
});
};
fetchData();
}, []);
I think the problem is that you provide an async function as a callback to useEffect, which is not allowed. Just get rid of it, like so:
useEffect(() => {
setLoading(true);
const fetchData = async () => {
// Also, you either await or use .then(), not both
const response = await getConfigs();
const obj = response;
setRedux(obj[0]);
};
fetchData();
}, []);
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);
})()