How to combine two json apis in react based on id - reactjs

I am trying to combine two json apis based on the id value. Is there a way I could achieve that?
Thanks. Below is my section of the code I have attempted so far:
const [data, setdata] = useState([])
const [runs, setruns] = useState([])
//get data from the first api
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get('http://localhost:8000/tasks?format=json');
setdata(res.data['results']);
} catch (e) {
console.log(e)
}
}
fetchData();
}, []);
//map the rows of data from the api above to obtain values based on id value
useEffect(() => {
data.map(row => {
console.log(row.id)
const fetchRuns = async () => {
const res2 = await axios.get(`http://localhost:8000/task/${row.id}/runs`);
setruns(res2.data)
}
fetchRuns();
row.rundata = runs
console.log('row:', row)
})
}, []);

You can make the second request in the first useEffect as well and then store everything together
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get('http://localhost:8000/tasks?format=json');
const arr = [];
res.data.result.map(row => {
arr.push(axios.get(`http://localhost:8000/task/${row.id}/runs`));
}
const res2 = await axios.all(arr);
setdata(); // here you will need to join both results, but to help you better we will need the structure of both
} catch (e) {
console.log(e)
}
}
fetchData();
}, []);

So if I understand correctly, you have first an API call that will provide you with a list of IDs and you need to populate (get the data) from those IDS based on a second API call.
You need to pass "data" in the dependencies of your second useEffect. This tells React "whenever 'data' changes, please do the following".
Also, you should set the data at the end of your loop or you'll end up changing it every iteration with 1 value!
Anyway, you should probably use the "for await" syntax as async logic is not easily compatible with .map.
const [data, setdata] = useState([])
const [runs, setruns] = useState([])
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get('http://localhost:8000/tasks?format=json');
setdata(res.data['results']);
} catch (e) {
console.log(e)
}
}
fetchData();
}, []);
async function populate(data){
let populatedData = []
for await (let row of rows){
const res2 = await axios.get(`http://localhost:8000/task/${row.id}/runs`)
populatedData.push(res2.data)
}
setruns(populatedData)
}
useEffect(() => {
if (data.length === 0) return
populate(data)
},[data])
Let me know if it works!

Related

Map data on runtime after post request

I have three apis in all. GetAssets is the first, followed by assetsOptionsList and getAssetsLibrary. The issue I'm having is that when I post the data on getAssetsLibrary, I want to be able to present it on get Assets at runtime.Everything is working fine but i want to show assets on runtime.
I'm setting the runTime state true on get request but the problem is it works only for one time.Second time, it does not map on runtime. Actually, i want to know is there any alternative so that i can achieve the goal.
In the below code the one function is getting the assets. And i want to run the one function when the post request successfully sent.
const [images, setImages] = useState([]);
const [assetOptions, setAssetOptions] = useState([]);
const [faqOpened, setToggleFaq] = useState(false);
const [runTime, setRunTime] = useState(false)
const [assetID, setAssetID] = useState()
const [isLoading, setIsLoading] = useState(false);
const handleForm = (e) => {
const index = e.target.selectedIndex;
const el = e.target.childNodes[index]
const option = el.getAttribute('id');
setAssetID(option)
}
const formHandler = (e) => {
e.preventDefault()
let formData = new FormData();
formData.append('media', e.target.media.files[0]);
formData.append('assetListId', assetID)
formData.append('name', e.target.name.value);
console.log(Object.fromEntries(formData))
const res = axios.post('api/asset-library',
formData
).then((response) => {
showSuccessToaster(response?.data?.message)
setRunTime(true)
setToggleFaq(false)
})
.catch((error) => {
showErrorToaster(error?.response?.data?.message)
})
}
const showSuccessToaster = (response) => {
return uploadToasterSuccess.show({ message: response });
}
const showErrorToaster = (error) => {
return uploadToasterError.show({ message: error });
}
const one = async () => {
setIsLoading(true)
const data = await axios.get('api/assets').then((res) => {
return res?.data?.data
})
setImages(data)
setIsLoading(false)
}
const two = async () => {
const data = await axios.get('/api/asset-list').then((res) => {
return res?.data?.data
})
setAssetOptions(data)
}
useEffect(() => {
one()
two()
}, [runTime]);

How to combine multiple API requests in one function with fetch() in React

I have to functions getDataOne and getDataTwo. How do I combine below into one function, using fetch(), useState and useEffect?
const MyComponent = () => {
const [loading, setLoading] = useState(false);
const [dataOne, setDataOne] = useState<Data[]>([]);
const [dataTwo, setDataTwo] = useState<Data[]>([]);
const getDataOne = async () => {
setLoading(true);
const result = await fetch(
"https://my-api-link-one"
);
const jsonResult = await result.json();
setLoading(false);
setDataOne(jsonResult);
};
const getDataTwo = async () => {
setLoading(true);
const result = await fetch(
"https://my-api-link-two"
);
const jsonResult = await result.json();
setLoading(false);
setDataTwo(jsonResult);
};
useEffect(() => {
getDataOne();
getDataTwo();
}, []);
Update:
I set it up using Promise.all
const [loading, setLoading] = useState(false);
const [dataOne, setDataOne] = useState<DataOne[]>([]);
const [dataTwo, setDataTwo] = useState<DataTwo[]>([]);
const [data, setData] = useState<DataOne[] & DataTwo>([]);
const urls = [
"https://url-one", "https://url-two",
];
const getData = async () => {
setLoading(true);
const results = await Promise.all(
urls.map((url) => fetch(url).then((res) => res.json()))
);
setLoading(false);
setData(results);
console.log(data);
};
This is not totally working yet. How do I use useState now correctly (and handle both data from urls)? In the end I want to have one data variable so I can map over this variable:
{data.map((item) => {
return (
// etc
So, Promise.all() accepts an array of promises, so naturally Promise.all() returns an array only. So even though your results variable still is an array I would recommend destructuring it because in this case there are only two API fetches involved. Looking at your update, I think there's only small modifications left which are as follows :
const urls = ["https://url-one", "https://url-two",];
const getData = async () => {
setLoading(true);
const [result1, result2] = await Promise.all(
urls.map((url) => fetch(url).then((res) => res.json()))
);
setLoading(false);
setDataOne(result1);
setDataTwo(result2);
console.log(data);
};
You can use Promise.all. Read more here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all.
const getData = () => {
setLoading(true);
Promise.all([fetch('api-1'), fetch('api-2')]).then(results => {
setDataOne(results[0]);
setDataTwo(results[1]);
}).finally(() => setLoading(false));
}
Utilize .flat() to reformat the data array returned from the Promise.all() into your state which holds the response obj/array,
Promise.all(
urls.map(url =>
fetch(url).then(e => e.json())
)
).then(data => {
finalResultState = data.flat();
});

React Hook useEffect has a missing dependency: 'fetchUser'. useEffect problem?

I'm new to react and I'm learning how to use useEffect. I encountered this warning in my react app. I tried out some solutions on SO but the warning still remains. Both fetchUser and fetchPosts trigger this warning. Can anyone enlighten me what is the problem and what does the warning mean?
App.js
useEffect(() => {
setLoading(true)
const getUser = async () => {
const userFromServer = await fetchUser()
if (userFromServer) {
setUser(userFromServer)
setLoading(false)
} else {
console.log("error")
}
}
getUser()
}, [userId])
useEffect(() => {
const getPosts = async () => {
const postsFromServer = await fetchPosts()
setPosts(postsFromServer)
}
getPosts()
}, [userId])
useEffect(() => {
const getUserList = async () => {
const userListFromServer = await fetchUserList()
setUserList(userListFromServer)
}
getUserList()
}, [])
// Fetch user
const fetchUser = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
const data = await res.json()
return data
}
// Fetch posts
const fetchPosts = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
const data = await res.json()
return data
}
// Fetch list of users
const fetchUserList = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users/')
const data = await res.json()
return data
}
If you are using any function or state which has been declared outside the useEffect then you need to pass it in the dependency array like this:
const someFunctionA = () => {
....
}
const someFunctionB = () => {
....
}
useEffect(() => {
....
}, [someFunctionA, someFunctionB])
You can read more about it here in case you want to know how it will be rendered: React useEffect - passing a function in the dependency array

Fetch and setInterval react hooks problem

I recently used hooks with React to fetch data from server but i'm facing a problem with hooks. The code seems correct but it look like the useEffect isn't called at first time but 3 seconds after with the setInterval. I have blank table for 3 seconds before it appear. I want to directly show the data and call it 3 seconds later.
What is the correct way to use it ?
const [datas, setDatas] = useState([] as any);
useEffect(() => {
const id = setInterval(() => {
const fetchData = async () => {
try {
const res = await fetch(URL);
const json = await res.json();
setDatas(jsonData(json));
} catch (error) {
console.log(error);
}
};
fetchData();
}, TIME)
return () => clearInterval(id);
}, [])
You need to invoke fetchData once initially outside the interval. Define fetchData outside the interval.
useEffect(() => {
// (1) define within effect callback scope
const fetchData = async () => {
try {
const res = await fetch(URL);
const json = await res.json();
setDatas(jsonData(json));
} catch (error) {
console.log(error);
}
};
const id = setInterval(() => {
fetchData(); // <-- (3) invoke in interval callback
}, TIME);
fetchData(); // <-- (2) invoke on mount
return () => clearInterval(id);
}, [])
With React Hooks:
const [seconds, setSeconds] = useState(0)
const interval = useRef(null)
useEffect(() => { if (seconds === 60) stopCounter() }, [seconds])
const startCounter = () => interval.current = setInterval(() => {
setSeconds(prevState => prevState + 1)
}, 1000)
const stopCounter = () => clearInterval(interval.current)

What's the best way to do request multiple data from Firebase in React

I was wondering what would be the best way to get multiple data from Firebase in an async function to wait for some data from the first request. I'm using this code right now but it's not reliable and it breaks sometimes saying that it can't fetch the data for the second call as it's undefined.
function useOccasion() {
const [occasionData, setOccasionData] = useState(null)
const [friend, setFriend] = useState(null)
let { occasion } = useParams()
useEffect(() => {
const unsubscribe = firestore.collection('occasions').doc(occasion)
.onSnapshot(async eventData => {
setOccasionData({id: eventData.id, ...eventData.data()})
let friendData = await firestore.collection("friends").doc(eventData.data().friend).get();
setFriend({id: friendData.id, ...friendData.data()});
})
return () => unsubscribe()
}, [occasion])
return [occasionData, friend]
}
If there's a more robust way to achieve this that would be amazing.
I'd suggest breaking your data fetches into two hooks, one for each collection occasion and friends.
I'm not sure how you've set firebase up, but I access it through context.
e.g.
const useOccasion = () => {
const firebase = useContext(FirebaseContext)
const [occasions, setOccasions] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const unsubscribe = firebase.db.collection('occasions')
.onSnapshot(snapshot => {
if (snapshot.size) {
let occasionList = []
snapshot.forEach(doc =>
occasionList.push({ ...doc.data(), uid: doc.id }),
)
setOccasions(occasionList)
setLoading(false)
} else {
setOccasions([])
setLoading(false)
}
})
return () => {
unsubscribe()
}
}, [])
return { occasions, loading }
}
Finally, in your component where you require the data you can access this hook:
const { occasions, loading } = useOccasion()

Resources