API returns JSON? Or something else? - javascript

I'm building a simple React app for a class project using the National Weather Service API. The idea is: get an API return for a set of latitude and longitude coordinates (which I'll expand later so that users can input their address and a separate geocoding API can return their lat/long coordinates), which contains the URL to the forecast data (using grid coordinates), use that URL to get a second API return (believe it or not this is how NWS says it should be done) which is an object which contains a nested object properties that contains a nested array periods of objects that each represent a day/night forecast, each identified with a name and number.
I have the following code successfully displaying a list of the period names (to start), but I am getting an error in console: Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0.
Internet searches (including stackoverflow) tell me this occurs because the return isn't in JSON format, but all the resources I can find on the NWS API says it returns JSON. If it didn't; I don't think I'd get anything displaying on the web page (right?), so I must be getting JSON back since I'm seeing the period names.
In addition, the names only display when I update the browser that's displaying my live server. If I refresh the page it disappears. What's going on?
import React, { useState, useEffect } from 'react'
function GetForecast() {
const [gridEndpoint, setGridEndpoint] = useState('');
const [forecastArray, setForecastArray] = useState([]);
useEffect(() => {
fetch(`https://api.weather.gov/points/45.5312,-122.6447`)
.then(reply => reply.json())
.then(json => json.properties.forecast)
.then(link => setGridEndpoint(link))
.then(
fetch(`${gridEndpoint}`)
.then(reply => reply.json())
.then(json => json.properties.periods)
.then(array => setForecastArray(array))
)
}, [])
return (
<div>
{forecastArray.map((period, index) => (
<div key={index}>{period.name}
</div>
))}
</div>
)
}

Had someone point out to me that setGridEndpoint() isn't updating gridEndpoint the way I had it written. Here's what I replaced it with.
useEffect(() => {
fetch(`https://api.weather.gov/points/45.5312,-122.6447`)
.then(reply => reply.json())
.then(json => json.properties.forecast)
.then(link => fetch(link))
.then(reply => reply.json())
.then(json => json.properties.periods)
.then(array => setForecastArray(array))
}, [])

Related

I can't access the elements of an array via bracket notation in the frontend but it works in the backend

I am new to coding and I am experiencing a really strange problem. There is an array coming from a database in the backend and I want to access the single elements of the array in the frontend via bracket notation. I can access the single elements of the array in the backend. But in the frontend, I receive an error message when I try to access the elements.
Here is my GitHub repo for the project: https://github.com/LittleWing85/Homepage_For_A_Friend
And you can see the website here: https://website-for-portfolio.herokuapp.com/
In /client/src/modules/Project.js, I am retreiving an object with data from the backend and storing the object in a variable called project:
export default function Project() {
const dispatch = useDispatch();
const projectId = useSelector((state) => state.portfolio.projectId);
const [project, setProject] = useState({});
useEffect(() => {
fetch("/api/project/" + projectId)
.then((response) => response.json())
.then((data) => {
setProject(data);
});
}, [projectId]);
The object has a property with the key gallery_pictures and its value is an array. With this code I was trying to display the first element of that array:
return (
<div className="content">
...
<p>{project.gallery_pictures[0]}</p>
In the console of my browser I receive the error message project.gallery_pictures is undefined:
screenshot of error message
But if I change the code <p>{project.gallery_pictures[0]}</p> to <p>{project.gallery_pictures}</p>, the content of the array is displayed:
screenshot of displayed content from array
For this reason I don't understand why the console says that project.gallery_pictures is undefined.
I tried to access the elements of this array in the backend console in /server/server.js with the same approach in this code and it worked fine:
const express = require("express");
...
app.get("/api/project/:id", (request, response) => {
getProjectDataById(request.params.id).then((result) => {
response.json(result);
console.log(Array.isArray(result.gallery_pictures));
console.log(result.gallery_pictures[0]);
> });
> });
screenshot of working code in backend
What is it that I am not getting? Why can't I access the elements of the array in the frontend?
Within the useEffect you fetch data from your sever and, as soon as you receive a response, set project to the received data. However, while waiting for the server response project is still the initial value {}, thus project.gallery_pictures is still undefined.
As a solution, you could add a condition that checks if project is still undefined, and if yes instead render a loading screen.
This is happening because the request hasn't completed by the time that your component attempts to render itself. Inside your component adding a condition will do the trick
{project?.gallery_pictures !== undefined && Array.isArray(project.gallery_pictures) ? project.gallery_pictures[0] : null}

How to fetch data properly for this API? (array)

console.log screenshot
Hi, I am using "fetch" method with API for my website, and this API shows book information, if books include the input title.
Screenshot attached is console.log result when typing an example book title.
I'd like to get every title info of each array, could anyone can help me on this?
Especially I am not sure what is the proper line for this.
.then((data) => {document.getElementById("bookinfo").innerHTML=
data['documents'.authors];
Entire script for fetch is below.
<script>
function getBook(){
let bookvalue=document.getElementById("book").value;
fetch('https://dapi.kakao.com/v3/search/book?target=title&query=' + bookvalue, {
headers: {
Authorization: "KakaoAK TokenID"
}
})
.then((response) => response.json())
.then((data) => {document.getElementById("bookinfo").innerHTML=
data['documents'.authors];
});
}
</script>
You're not calling the array index correctly, data['documents'.authors] should be data.documents[0].authors to get the author of the first item and data.documents[1].authors to get the second, etc...
What do you intend to do with the titles?
EDIT: Fixed it for easier use (I think this is what you want)
.then(data => data.documents.map(book => book.title)).then(titles => document.getElementById("bookinfo").innerHTML = titles.join(", "));
Otherwise create an array
const titles = [];
push into array
.then(data => data.documents.map(book => titles.push(book.title))
But you might have issues with this and get empty array since the promise might still be pending

Reddit API Pagination in React

I am currently trying to build a Reddit client with React. For those of you that are not familiar with Reddit, it's basically a social network that consists of thousands of communities or "subreddits" created by users. Each subreddit has posts that can contain text, images, videos or links, and can be found under the URL reddit.com/r/<subreddit_name>. Reddit's public API is very simple in that you can just add .json to this URL to get a JSON response containing the posts. Specifically, the relevant data lies at response.data.children, which is an array of objects representing the posts.
Reddit doesn't have pagination in that I can just call the API and pass something like ?page=42 as a query string. Instead, I have to pass a query string containing the value of the response.data.after property of the previous response, which is basically the ID of the last post of that response, so that Reddit can now send me posts starting with the next one after that.
Currently, my custom hook to call the API looks like this:
function usePosts(community) {
const [fetching, setFetching] = useState(false);
const [error, setError] = useState(null);
const [posts, setPosts] = useState([]);
useEffect(() => {
setFetching(true);
getPosts(community)
.then(value => {
setError(null);
setPosts(value.data.children.map(value => value.data));
})
.catch(error => setError(error))
.finally(() => setFetching(false));
}, [community]);
return [fetching, error, posts];
}
The getPosts() method simply fetches the data from the API and does some error handling in case the response is not okay.
Now on to my question: How can I best implement it so that I can not only call this hook to load the first posts (which I am already doing), but also have some way of storing the after property, and then call the hook again when I want the next few posts to be appended to the posts array? Or do I need another hook? And do I have to return after as well if I want to store it, since I am mapping the response to be only the post data? How would you solve this?

Fetch response and parse to json getting html instead of the JSON data at the URL

I am clearly getting HTML here and I can't understand why exactly. This is the function that's expected to fetch the data and parse the URL to json but even though the console shows the URL when using console.log(res) when I try res.json() I get a rejected promise
const fetchData = async () => {
navigator.geolocation.getCurrentPosition(function(position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
await fetch(`${process.env.REACT_APP_API_URL}/weather?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}`)
.then(res => console.log(res)) //expected URL appears in the console.
}
fetchData();
}, [lat,long])
I tried res.text() to see the response that's being fetched and >concluded that I am getting the HTML file from the public directory. (I >assumed it was HTML considering the unexpected token.) I don't understand >why it's not parsing to JSON and instead, it's fetching the HTML. console HTML info I see this in the console but am not sure what to do from here
Also in the console of the app I checked fetch('https://api.openweathermap.org/data/2.5/weather?lat=28.052684799999998&lon=-82.427904&APPID=d8c1409b5342d12f52e6dce35fc26aac') and a pending promise was returned. console results
I uploaded the repo for better understanding
Is there some confusion happening because of the environmental variables?
As I know nothing about the url you are about to fetch, I can only guess that you have error in the url format and the backend reject you. Even if the server sends json data in normal situation, if you have an error in your url, it will send error pages like server error, 404 error, things like that.
Copy and paste the url in the new browser tab and check the response. I think you might get a error page.
Because the document Your fetching is in HTML format Not in JSON format
HTML
Start's with <!DOCTYPE html> (brackets) {throws Error}
JSON
format : {"latitude":"-0289201019", "longitude":"-0289201019"}
Use the res.text() to debug
Well, this seems to be a little complicated as I could not get the result like you. But I think there is a way.
As you mentioned that it is returning HTML format, first convert it to html using this.
var html = new DOMParser().parseFromString(data, "text/xml")
html.documentElement will return HTML like this.
<html>
<head></head>
<body></body>
Use console.log to see the result. If the json string is in the body, you can use normal javascript functions to get results.
For example, you can get body content using
html.documentElement.getElementsByTagName('body')[0].textContent
I'm not sure this is a solution, but there are a few issues with your current code.
//create two states for longitude and latitude
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
//const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
navigator.geolocation.getCurrentPosition(function(position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
await fetch(`${process.env.REACT_APP_API_URL}/weather?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}`)
.then(res => console.log(res))
}
fetchData();
}, [lat,long])
Why use an empty array as default state for lat and long? Latitude and longitude are numbers. Either represent them as 0, or use null to represent that the value is not yet loaded.
const [lat, setLat] = useState(null);
const [lon, setLon] = useState(null);
I will be using null to represent an unloaded value.
useEffect(callback, [lat, lon]) will be triggered whenever lat, lon, or both change. You will then call getCurrentPosition() and override the changed values. That isn't a logical thing to do. Loading the current position is a good starting position, but whenever a user change lat or lon I assume you want fetch the weather of the provided location.
You are currently overriding lat/lon with the current location each time lat/lon change. The best thing here is split up the actions:
// load the current location only on component mount as starting point
useEffect(() => {
navigator.geolocation.getCurrentPosition(function(position) {
setLat(position.coords.latitude);
setLon(position.coords.longitude);
});
}, []); // <- empty dependency list
// retrieve new weather info each time lat or lon changes
useEffect(() => {
// Make sure both lat and lon are present before firing a fetch request.
// Can't use truthy/falsy because 0 is a valid coordinate, but is falsy.
if (lat == null) return;
if (lon == null) return;
fetch(`.../weather?lat=${lat}&lon=${lon}&...`)
.then(res => res.json())
.then(data => console.log(data));
}, [lat, lon]); // <- having lat/lon as dependencies

How to use array items as parameter for API call

I am currently trying to log out the names and ID of a various items for a video game using an array which holds the ID of each item.
Currently I have the following.
const URL =
"http://services.runescape.com/m=itemdb_oldschool/api/catalogue/detail.json?item=";
const items = ["4151", "2"];
items.map(item => {
fetch(`${URL}${item}`)
.then(data => data.json())
.then(({ item: { id, name } }) => console.log(`${id}: + ${name}`));
});
I should be getting 4151: Abyssal Whip as a format for each item, but it isnt working. I have done this in the past using a cryptocurrency api, but I cant get it to work here, and I am not sure where I am going wrong.
Thanks for the help in advance.
Some more detail would help. If you open dev tools and look at console output when hitting the API, you might be getting an error... maybe a Mixed-Content error? The runescape API is returning from an HTTP connection. If you are requesting from an HTTPS connection, it will not allow the resource to be delivered. Otherwise, your code should run (but the +) won't be necessary because you're using template literals.
I am sure its a typical CORS policy error. Here's how I managed to fetch the data. The heroku API enables cross-origin requests to anywhere.
const items = ["4151", "2"];
const proxyUrl = "https://cors-anywhere.herokuapp.com/",
targetUrl =
"http://services.runescape.com/m=itemdb_oldschool/api/catalogue/detail.json?item=";
items.map((item) => {
fetch(proxyUrl + targetUrl + item)
.then((data) => data.json())
.then(({ item: { id, name } }) => console.log(`${id}: + ${name}`));
});

Categories