useEffect calling API on page load errors - reactjs

I'm working on a project that utilizes the IGDB to display information about various video game titles. I've got an API call on the search page that returns the information appropriately. Upon clicking on one of those titles from the search, I load a new page with detailed information about the game. The API call for the detailed information is the exact same as the one for the search functionality (exception being that they retrieve different fields from the DB, aside from that, exactly the same) and both return the data appropriately and in the exact same format (an array of objects.) However, I have had a lot of trouble displaying the data on the game details page.
Here is the response that I am having trouble displaying:
And, just for the sake of as much detail for you guys as possible, here is the successfully displayed response from the search page:
The way I have it set up is to run a useEffect hook on page load. The hook does run, and the data is returned, but displaying the data has been a challenge. Here is the current hook.
useEffect(() => {
async function getGameId(gameId) {
const response = await getSpecificGame(gameId);
if (!response.ok) {
throw new Error('Something went wrong...');
}
const result = response.json();
const gameData = result?.map((game) => ({
gameId: game.id,
name: game.name,
cover: game.cover,
summary: game.summary,
platforms: game.platforms,
genres: game.genres,
}));
setSelectedGame(gameData);
}
getGameId(gameId);
}, [])
With this code I receive the following error:
Uncaught (in promise) TypeError: result.map is not a function
With the error being with result.map, I'm very lost on where to go from here. I have wondered if perhaps the response.json() line is unnecessary or misplaced, but with the data returning in the exact same fashion as it does for the search page, I'm not sure what I would need to change. The error is not thrown on the response, meaning the data comes back ok, but it is thrown on the result.map() line, meaning either the result = response.json() is somehow incorrect/unnecessary, or I'm missing another line that needs to be there. This confuses me greatly though, since the other API call I make to perform the search works and is set up the same way. For extra context, I will post the properly functioning API call from the search page as well, this one is within a form handler:
const handleFormSubmit = async (event) => {
event.preventDefault();
if (!searchInput) {
return false;
}
try {
const response = await getGame(searchInput);
if (!response.ok) {
throw new Error('Something went wrong...');
}
const result = await response.json();
const gameData = result.map((game) => ({
gameId: game.id,
name: game.name,
cover: game.cover,
}));
setSearchedGames(gameData);
setSearchInput('');
} catch (err) {
console.error(err);
}
};
Here are the API calls that go with those functions.
export const getGame = (searchInput) => {
return fetch(`https://id.twitch.tv/oauth2/token?client_id=************&client_secret=****************&grant_type=client_credentials`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
const accessToken = data.access_token;
return fetch(`https://fathomless-river-46653.herokuapp.com/https://api.igdb.com/v4/games/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Client-ID': '***********',
'Authorization': `Bearer ${accessToken}`
},
body: `
search "${searchInput}";
fields name,cover.url;`
})
});
};
export const getSpecificGame = (gameId) => {
return fetch(`https://id.twitch.tv/oauth2/token?client_id=************&client_secret=**************&grant_type=client_credentials`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
const accessToken = data.access_token;
return fetch(`https://fathomless-river-46653.herokuapp.com/https://api.igdb.com/v4/games/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Client-ID': '**************',
'Authorization': `Bearer ${accessToken}`
},
body: `
fields name,summary,platforms.name,genres.name,cover.url;
where id = ${gameId};`
})
});
}
This seems to be a simple matter of appropriately changing the syntax to function with useEffect instead of a form handler, but it is proving to be quite the challenge. I've looked over probably 20 threads of similar issues, but none of which have been able to solve my problem.
I've wondered if I should go another route entirely, completely removing the useEffect hook and trying something else, but this feels like a learning opportunity on how to make use of useEffect so I'm trying my best to get this working.
I would be happy to attach any extra code to assist with an answer. I posted a very similar question yesterday and received some helpful responses, but since then have made progress and reached another roadblock point. Thank you in advance to anyone who is able to assist!

const result = response.json();
There is a small error here, you need to await it:
const result = await response.json();
In the other example you posted from a form handler, it's awaited correctly, so it works.

Related

Accessing Data from my axios get response not working

can someone point out where am going wrong?
I have a simple API get request. this get data fine from my API
const GetMedicalPackages = async (props:IGetMedPack)=>{
const token = props.token
const data = axios({
method:"get",
responseType:"json",
url : baseURL + "medicalPackage",
headers: { "Authorization": `Bearer ${token}`}
})
await data;
return data.then((response:IMedicalPackageData| undefined)=>{
return response
})
}
This returns data like this
Data from API
Now trying to access the data with this code returns with this code always returns and undefined
useEffect(() => {
//async function fetchMyData() {
GetMedicalPackages({ token }).then((response) => {
if (response) {
const options = response.data.map((row) => {
console.log(row.MedicalInsurancePackage);
return row.MedicalInsurancePackage;
//console.log(row);
//options.push(row.MedicalInsurancePackage);
});
//setMedPackage(options);
console.log(options, response.data);
}
});
options values
I suspect it to do with the object that returned in each row but i might be wrong.
Can someone point out what is wrong please thanks
I have tried foreach and various techniques but nothing.
to get the array of strings
Just for clarity, The problem is not the axios call giving me the data. I can see data fine. But try to use the data returned is the problem. console log shows me the data. but as soon i try to map it i get undefines. Please see images
console log row give me this
activePackage: true
medicalInsurancePackage: "Standard"
medicalInsurancePackageDesc: "S Standard"
medicalInsurancePackageID:1
[[Prototype]]:Object
but console log row.medicalInsurancePackage give me undifined
Looks like your are missunderstanding how Promise are working. consider doing:
const GetMedicalPackages = async (props) => {
const token = props.token;
return await axios({
method: "get",
responseType: "json",
url : baseURL + "medicalPackage",
headers: { Authorization: `Bearer ${token}` },
});
};
You shouldn't be awaiting your variable, but should be awaiting for the result of your axios call. And then just return the response you get from it.
const data = await axios(...)
return data;

sessionStorage not available immediately after navigate

I am trying to implement an React solution with Strapi as backend where authorization is done using JWT-keys. My login form is implemented using the function below:
const handleLogin = async (e) => {
let responsekey = null
e.preventDefault();
const data = {
identifier: LoginState.username,
password: LoginState.password
}
await http.post(`auth/local`, data).then((response) => {
setAuth({
userid: response.data.user.id,
loggedin: true
})
responsekey = response.data.jwt
setLoginState({...LoginState, success: true});
sessionStorage.setItem('product-authkey', responsekey);
navigate('/profile');
}).catch(function(error) {
let result = ErrorHandlerAPI(error);
setLoginState({...LoginState, errormessage: result, erroroccurred: true});
});
}
The API-handler should return an Axios item which can be used to query the API. That function is also shown below. If no API-key is present it should return an Axios object without one as for some functionality in the site no JWT-key is necessary.
const GetAPI = () => {
let result = null
console.log(sessionStorage.getItem("product-authkey"))
if (sessionStorage.getItem("product-authkey") === null) {
result = axios.create(
{
baseURL: localurl,
headers: {
'Content-type': 'application/json'
}
}
)
} else {
result = axios.create({
baseURL: localurl,
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${sessionStorage.getItem("product-authkey")}`
}
})
}
return result
}
export default GetAPI()
However, once the user is redirected to the profile page (on which an API-call is made which needs an JWT-key), the request fails as there is no key present in the sessionStorage. The console.log also shows 'null'. If I look at the DevTools I do see that the key is there... And if I refresh the profile page the request goes through with the key, so the key and backend are working as they should.
I tried making the GetAPI function to be synchronous and to move the navigate command out of the await part in the handleLogin function, but that didn't help.
Does someone have an idea?
Thanks!
Sincerely,
Jelle
UPDATE:
Seems to work now, but I need to introduce the getAPI in the useEffect hook, I am not sure if that is a good pattern. This is the code of the profile page:
useEffect(() => {
let testapi = GetAPI()
const getMatches = async () => {
const response = await testapi.get(`/profile/${auth.userid}`)
const rawdata = response.data.data
... etc
}, [setMatchState]
export default GetAPI() this is the problematic line. You are running the GetApi function when the module loads. Basically you only get the token when you visit the site and the js files are loaded. Then you keep working with null. When you reload the page it can load the token from the session storage.
The solution is to export the function and call it when you need to make an api call.

Destructure array to display API results

I'm trying to display results from an API but it seems like an entire section of the page isn't being returned. There are no errors present so solving this has been somewhat confusing.
My assumption is that based on the way the API has returned the data, my current setup for destructuring the data is incorrect. Here is the response I receive in the console from the API.
I want to apend these results to a page to display the name and cover of these video game titles.
Section displaying results
<Container>
<h2>
{searchedGames?.length
? `Viewing ${searchedGames.length} results:`
: 'Search for a game to begin'}
</h2>
<CardColumns>
{(searchedGames || []).map((game) => {
return (
<Card key={game.gameId} border='dark'>
{game.cover ? (
<Card.Img src={game.cover} alt={`The cover for ${game.name}`} variant='top' />
) : null}
<Card.Body>
<Card.Title>{game.name}</Card.Title>
</Card.Body>
</Card>
);
})}
</CardColumns>
</Container>
Form Handler
const handleFormSubmit = async (event) => {
event.preventDefault();
if (!searchInput) {
return false;
}
try {
const response = await getGame(searchInput);
if (!response.ok) {
throw new Error('Something went wrong...');
}
const { result } = await response.json();
const gameData = result?.map((game) => ({
gameId: game.id,
name: game.name,
cover: game.cover.url,
}));
setSearchedGames(gameData);
setSearchInput('');
} catch (err) {
console.error(err);
}
};
Upon searching, I receive the API response with the appropriate data, but the page does not display anything at all (not even the "Viewing xxx number of results" section, which I feel is a clue pointing to the searchedGames array.) I'd be happy to add any extra information or code if someone would like. I feel like there is a simple solution but I have been unable to find the method to fix this. Thank you in advance for any assistance anyone can offer!
EDIT:
There was a helpful response here recommending I check my searchedGames array and, once I did, I saw that it was empty. So the data from the API is not getting put into the "searchedGames" array. I am researching how to fix this, but if anyone responds to the question I feel this may be a helpful detail to assist. The problem is that my searchedGames array is not being filled with the API data.
EDIT 2:
export const getGame = (searchInput) => {
return fetch(`***********`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
const accessToken = data.access_token;
return fetch(`https://fathomless-river-46653.herokuapp.com/https://api.igdb.com/v4/games/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Client-ID': '********',
'Authorization': `Bearer ${accessToken}`
},
body: `
search "${searchInput}";
fields name,cover.url;`
})
});
};
Not entirely sure if there are details missing on the actual API response data format but I'll try giving an answer.
The json() method of fetch returns the response body of your HTTP request as JSON. So with your response data being the one of your first screenshot, it's an array of objects but not an object with an result attribute. However, latter is what you assume in your code:
const { result } = await response.json();
Then you proceed as if result was the response array of your first screenshot while it actually is undefined, so that gameData resolves to undefined after optional chaining.
const gameData = result?.map((game) => ({
gameId: game.id,
name: game.name,
cover: game.cover.url,
}));
Try out using the JSON response directly without destructuring. This should give you the actual array of game objects:
const result = await response.json();

Code after fetch call is seemingly not read

I'm making a POST request to a local Express server with a JSON as its body and I'm doing this inside an async function:
async function sendSigninForm(login_form) {
const settings = {
method: 'POST',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(login_form)
};
try {
await fetch(`http://localhost:9000/signin`, settings)
} catch (e) {
return e;
}
}
I tried accessing fetch's response in various ways, like
const fetchResponse = await fetch(`http://localhost:9000/signin`, settings);
const data = await fetchResponse.json();
or
const fetchResponse = await fetch(`http://localhost:9000/signin`, settings)
.then((value) => console.log(value));
but neither of these work, and I can't even print anything.
const fetchResponse = await fetch(`http://localhost:9000/signin`, settings)
.then(console.log("hello"));
This works, but of course it won't let me print fetch's response so it's useless to me. Also, the server is apparently working correctly as it gets the form as required and queries the database correctly.
Well, I forgot to send status codes server-side, and apparently that's the reason it hanged, although everything seemed to be fine and the queries were actually executed.

Send array from React to Express/Node to query Mongo

I am having difficulty sending an array of Id numbers from React state, through Node/Express, then eventually to MongoDB.
The main difficulty is how to send an array from React to the server. Once I have it there in a usable array form, I believe I know how to query MongoDB using an array.
I have been trying to do this mainly with a GET method and have been unsuccessful using req.params or req.query. I have tried this with several versions of string templates, and using what the Axios docs say as well as many other answers on stack overflow, none have been successful.
I have also tried a couple of versions of a PUT request and also with no success.
The array of Ids already exists in props.completedJobs:
In React:
useEffect(() => {
const fetchData = async () => {
let completedJobsArray = props.completedJobs;
let json = JSON.stringify(data);
let getData = {jsonData: json};
const result = await axios('/api/brief/brief-array/' + getData);
setData(result.data);
};
fetchData();
}, []);
In Express app:
app.use("/brief", briefRouter);
then in the router:
router.get("/brief-array/:briefArray", briefController.brief_findArrayById);
then the function:
exports.brief_findArrayById = async (req, res) => {
try {
// console.log("req.body: ", req.body);
// console.log("req: ", req);
// console.log("req.query: ", req.query);
// console.log("req.params: ", JSON.stringify(req.params.briefArray));
const briefs = await GWJob.find({"_id": {$in: ["5d1297512c68bc49060bce7b", "5d1297092c68bc49060bce7a"] } });
res.json(briefs);
} catch (err) {
res.json({ message: err });
}
}
The MongoDB query works with the hard-coded IDs, but cannot get any of the above versions of console.logs to display any value other than undefined or "object object".
I am also unable to query the database using an array through Postman.
I expect to send 1 or more id numbers in an array to MongoDB, and receive those documents back to React on the front end. Any help with sending an array from state in React through Node/Express is much appreciated! Thank you.
You are sending Long string through the URL which is a limited length (~2047 char),
the safe away to accomplish what you are trying to do to use a post or put method and send the list in the body.
Example:
useEffect(() => {
const fetchData = async () => {
let completedJobsArray = props.completedJobs;
const result = await axios({
method: 'post',
url: '/brief-array',
data: completedJobsArray
});
setData(result.data);
};
fetchData();
}, []);
Another attempt, still returning undefined. If anyone has links to a thorough resource on sending/receiving data I would much appreciate it. I always have spent days trying to get one of these to work, until I started using Axios, but if I need to go beyond what I can do with Axios I am back to square one. Many thanks.
useEffect(() => {
const fetchData = async () => {
let completedJobsArray = props.completedJobs;
let queryData = {completedJobsArray};
const settings = {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8'},
body: JSON.stringify(queryData)
};
try {
const fetchResponse = await fetch(`/api/brief/brief-array`, settings);
const returnedData = await fetchResponse.json();
setData(returnedData);
} catch (error) {
console.error("error: ", error);
}
}
fetchData();
}, []);

Resources