How can I access 2 data attribute value using useRef hook? - reactjs

here I am confused about accessing 2 parameters on the endpoint.
/api/nad/buildingCount/:provinsi/:kota
Previously I was able to access 1 parameter using useRef, for example like this =
const provinsi = provinsiRef.current[index].dataset.value;
But when I tried for the 2 parameters, it was beyond my expectations.
This is my code, is there something wrong?. please for the solution, thank you
const handleKota = async (index) => {
try {
const provinsi = provinsiRef.current.dataset.value;
const kota = provinsiRef.current[index].dataset.value;
setIsLoading(true);
const result = await getBuildingOLDallKecamatan(provinsi, kota);
setDataKecamatan(result);
console.log(result);
} catch (error) {
} finally {
setIsLoading(false);
}
};

It seems that when you call handleKota, only one index is passed to the function, but the index in provinsiRef.current[index] and in kotaRef.current[index] are different, since these are 2 different ref array.
From your links to full code:
const handleKota = async (index) => {
try {
const provinsi = provinsiRef.current[index].dataset.value;
const kota = kotaRef.current[index].dataset.value;
setIsLoading(true);
const result = await getBuildingOLDallKecamatan(provinsi, kota);
setDataKecamatan(result);
console.log(result);
} catch (error) {
} finally {
setIsLoading(false);
}
};
As a result you might be getting mismatched provinsi and kota.
Possible solutions
Data attribute can carry a number of values. Therefore, you can give each kota item a value of provinsi they belong, such as data-provinsi. This way, you just need to access the kota ref, not both, to get everything needed for handleKota.
Instead of using ref to access data-set, use an approach that is not dependent on index. This does require some major refactor of existing code though. Perhaps check this answer for a quick example about accessing data-set with the event object.

Related

Const is not defined -> Yet standard solutions don't work

I want to display a mapped list where "UserName" is an entry value from a Firebase Realtime Database corresponding to the author of each entry.
The following code, inside the get(UsernameRef).then((snapshot) =>{}) scope, returns an undefined reference error as expected, 'UserName' is assigned a value but never used and 'UserName' is not defined
const [RecipeLibrary, setRecipeLibrary] = React.useState([]);
React.useEffect(() => {
const RecipeLibraryRef = ref(db, "Recipes/");
onValue(RecipeLibraryRef, (snapshot) => {
const RecipeLibrary = [];
snapshot.forEach((child) => {
const AuthorUserId = child.key;
child.forEach((grandChild) => {
const UserNameRef = ref(db, "Account/" + AuthorUserId + "/username");
get(UserNameRef).then((snapshot) => {
const UserName = snapshot.val();
});
RecipeLibrary.push({
name: grandChild.key,
author: UserName,
...grandChild.val(),
});
});
});
setRecipeLibrary(RecipeLibrary);
console.log({ RecipeLibrary });
});
}, []);
I've tried:
Using a React state to pass the variable -> Can't use inside React useEffect
Exporting and Importing a separate function that returns the desired UserName -> return can only be used in the inner scope
Moving the list .push inside the Firebase get scope -> React.useState can no longer access the list
I'm hoping there is a simple solution here, as I am new.
Your time and suggestions would mean a lot, thank you!
Update:
I got the RecipeLibrary array to contain the desired "UserName" entry, named author by moving the array .push inside the .then scope. Here is a log of that array at set (line 59) and at re-render (line 104).
child.forEach((grandChild) => {
const UserNameRef = ref(db, "Account/" + AuthorUserId + "/username");
get(UserNameRef).then((snapshot) => {
const UserName = snapshot.val();
RecipeLibrary.push({
name: grandChild.key,
author: UserName,
authorId: AuthorUserId,
...grandChild.val(),
});
});
});
});
setRecipeLibrary(RecipeLibrary);
console.log(RecipeLibrary);
However, now the mapped list is not rendering at all on screen.
Just some added context with minimal changes to original code, been stuck on this so long that I'm considering a full re-write at this point to jog my memory. Oh and here is the bit that renders the mapped list in case:
<Box width="75%" maxHeight="82vh" overflow="auto">
{RecipeLibrary.map((RecipeLibrary) => (
<Paper
key={RecipeLibrary.name}
elevation={3}
sx={{
etc...
This is a tricky one - the plainest option might be to move push() and setRecipeLibrary() inside the then() callback so they're all within the same scope, but that would have some terrible side effects (for example, triggering a re-render for every recipe retrieved).
The goal (which you've done your best to achieve) should be to wait for all the recipes to be loaded first, and then use setRecipeLibrary() to set the full list to the state. Assuming that get() returns a Promise, one way to do this is with await in an async function:
const [RecipeLibrary, setRecipeLibrary] = React.useState([]);
React.useEffect(() => {
const RecipeLibraryRef = ref(db, "Recipes/");
onValue(RecipeLibraryRef, (snapshot) => {
// An async function can't directly be passed to useEffect(), and
// probably can't be accepted by onValue() without modification,
// so we have to define/call it internally.
const loadRecipes = async () => {
const RecipeLibrary = [];
// We can't use an async function directly in forEach, so
// we instead map() the results into a series of Promises
// and await them all.
await Promise.all(snapshot.docs.map(async (child) => {
const AuthorUserId = child.key;
// Moved out of the grandChild loop, because it never changes for a child
const UserNameRef = ref(db, "Account/" + AuthorUserId + "/username");
// Here's another key part, we await the Promise instead of using .then()
const userNameSnapshot = await get(UserNameRef);
const UserName = userNameSnapshot.val();
child.forEach((grandChild) => {
RecipeLibrary.push({
name: grandChild.key,
author: UserName,
...grandChild.val(),
});
});
}));
setRecipeLibrary(RecipeLibrary);
console.log({ RecipeLibrary });
};
loadRecipes();
});
}, []);
Keep in mind that Promise.all() isn't strictly necessary here. If its usage makes this less readable to you, you could instead execute the grandChild processing in a plain for loop (not a forEach), allowing you to use await without mapping the results since it wouldn't be in a callback function.
If snapshot.docs isn't available but you can still use snapshot.forEach(), then you can convert the Firebase object to an Array similar to Convert A Firebase Database Snapshot/Collection To An Array In Javascript:
// [...]
// Change this line to convert snapshot
// await Promise.all(snapshot.docs.map(async (child) => {
await Promise.all(snapshotToSnapshotArray(snapshot).map(async (child) => {
// [...]
// Define this somewhere visible
function snapshotToSnapshotArray(snapshot) {
var returnArr = [];
snapshot.forEach(function(childSnapshot) {
returnArr.push(childSnapshot);
});
return returnArr;
}
Note that if get() somehow doesn't return a Promise...I fear the solution will be something less straightforward.

How to set data to the state after fetching from backend?

I want to get data from the backend and want to set those data to the state in ReactJS. Here is my source code
const [eachAsset, setEachAsset] = useState([]);
function ShowModalView(id)
{
axios.get("http://localhost:8070/assets/detail/"+id).then((res)=>{
const data = res.data
setEachAsset(data)
//console.log(eachAsset);
}).catch((err)=>{
console.log(err.message);
})
setShow2(true);
}
When I uncomment the console log, it shows an empty array. It means, setEachAsset(data) does not work properly. But I want to store data that are getting from the backend to the eachAsset state. What is the problem of this source code?
setEachAsset([...data])
I hope this would work
I would recommend using async-await which makes the code easier to read and understand the flow of the program as compared to promise chains.
const [eachAsset, setEachAsset] = useState([]);
const ShowModalView = async (id) => {
try {
const resp = await axios.get("http://localhost:8070/assets/detail/"+id);
setEachAsset(resp.data)
console.log(resp.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
setShow2(true);
}

How I can display only one object from API array Reactjs

How I can display only one object from [data.hits]? Ive tried map(), filter, also setRecipes([data.hits.recipe[0]]); etc ... and it doesn't work. Currently, it shows 10 objects on the website but I need only one. Thank you for any help.
const [recipes,setRecipes] = useState ([]);
useEffect(()=>{
getReciepes();
},[query]);
const getReciepes = async () => {
const response = await fetch (`https://api.edamam.com/search?
q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`);
const data = await response.json();
setRecipes(data.hits);
console.log(data.hits);}
I dont know how I did not notice this. Here is a simple solution :
const list=[...data.hits]
list.length = 1
setRecipes(list);
console.log(list);
}

duplicate entry while using push on array

The collaborators contain 3 data already and I am trying to push a user which should come first. I am baffled while I tried to use console.log(), I am getting 3 response values instead one. As illustrated below; it referenced to the line 25 and it printed trice.
However, when I pushed to collaborator, I am expecting a value but I got two data values pushed to collaborator. I just want one. I have spent several hours trying to figure what I have done wrongly. Please, I don't know how, that is why I posted here.
const resData = async() => {
const res = await directus.users.me.read()
const result = res.data
console.log(result)
collaborators.push(result)
}
resData()
I finally solved it by using useEffect.
useEffect(() => {
const resData = async() => {
const res = await directus.users.me.read()
const result = await res.data
collaborators.unshift(result)
}
resData()
},[collaborators])

In React, fetch data conditional on results of an initial fetch

We have written a custom data fetching hook useInternalApi which is similar to the useDataApi hook at the very bottom of this fairly decent tutorial on data fetching with react hooks. Our app fetches a lot of sports data, and in particular, we are trying to figure out the right data-fetching pattern for our use case, which is fairly simple:
Fetch general info for a specific entity (an NCAA conference, for example)
Use info returned with that entity (an array of team IDs for teams in the specific conference), and fetch info on each team in the array.
For this, our code would then look something like this:
import `useInternalApi` from '../path-to-hooks/useInternalApi';
// import React... and other stuff
function ComponentThatWantsTeamInfo({ conferenceId }) {
// use data fetching hook
const [conferenceInfo, isLoading1, isError1] = useInternalApi('conferenceInfo', { conferenceId: conferenceId })
// once conferenceInfo loads, then load info from all teams in the conference
if (conferenceInfo && conferenceInfo.teamsArray) {
const [teamInfos, isLoading2, isError2] = useInternalApi('teamInfo', { teamIds: conferenceInfo.teamIds })
}
}
In the example above, conferenceId is an integer, teamIds is an array of integers, and the combination of the 2 parameters to the useInternalApi function create a unique endpoint url to fetch data from. The two main problems with this currently are:
Our useInternalApi hook is called in an if statement, which is not allowed per #1 rule of hooks.
useInternalApi is currently built to only make a single fetch, to a specific endpoint. Currently, it cannot handle an array of teamIds like above.
What is the correct data-fetching pattern for this? Ideally, teamInfos would be an object where each key is the teamId for one of the teams in the conference. In particular, is it better to:
Create a new internal hook that can handle an array of teamIds, will make the 10 - 20 fetches (or as many as needed based on the length of the teamsArray), and will use Promise.all() to return the results all-together.
Keep the useInternalApi hook as is, and simply call it 10 - 20 times, once for each team.
Edit
I'm not sure if the underlying code to useInternalApi is needed to answer this question. I try to avoid creating very long posts, but in this instance perhaps that code is important:
const useInternalApi = (endpoint, config) => {
// Set Data-Fetching State
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
// Use in lieu of useEffect
useDeepCompareEffect(() => {
// Token/Source should be created before "fetchData"
let source = axios.CancelToken.source();
let isMounted = true;
// Create Function that makes Axios requests
const fetchData = async () => {
// Set States + Try To Fetch
setIsError(false);
setIsLoading(true);
try {
const url = createUrl(endpoint, config);
const result = await axios.get(url, { cancelToken: source.token });
if (isMounted) {
setData(result.data);
}
} catch (error) {
if (isMounted) {
setIsError(true);
}
} finally {
if (isMounted) {
setIsLoading(false);
}
}
};
// Call Function
fetchData();
// Cancel Request / Prevent State Updates (Memory Leaks) in cleanup function
return () => {
isMounted = false; // set to false to prevent state updates / memory leaks
source.cancel(); // and cancel the http request as well because why not
};
}, [endpoint, config]);
// Return as length-3 array
return [data, isLoading, isError];
};
In my opinion, if you need to use a hook conditionally, you should use that hook inside of a separate component and then conditionally render that component.
My understanding, correct me if I'm wrong, is that the initial API call returns an array of ids and you need to fetch the data for each team based on that id?
Here is how I'd do something of that sorts.
import `useInternalApi` from '../path-to-hooks/useInternalApi';
// import React... and other stuff
function ComponentThatDisplaysASpecificTeam(props){
const teamId = props.teamId;
const [teamInfo] = useInternalApi('teamInfo', { teamId });
if(! teamInfo){
return <p>Loading...</p>
}
return <p>do something with teamInfo...</p>
}
function ComponentThatWantsTeamInfo({ conferenceId }) {
// use data fetching hook
const [conferenceInfo, isLoading1, isError1] = useInternalApi('conferenceInfo', { conferenceId: conferenceId })
if (! conferenceInfo || ! conferenceInfo.teamsArray) {
return <p>this is either a loading or an error, you probably know better than me.</p>
}
// Let the data for each team be handled by its own component. This also lets you not have to use Promise.all
return (
<div>
{conferenceInfo.teamIds.map(teamId => (
<ComponentThatDisplaysASpecificTeam teamId={teamId} />
))}
</div>
)
}

Resources