I am using a Next.js API call to retrieve some data from an external API (I know this is better done with getStaticProps but I need to make a few calls before passing the data to the client.)
I don't understand why doe the backend function runs multiple times, and on the first time it doesn't have the POST data.
cleint
export default function Customer () {
const router = useRouter()
const { id: submissionId } = router.query
const { data: customer, error } = useSWR(['/api/jotform/get-client/', submissionId], fetcher)
...
server
export default async function handler (req, res) {
try {
const customers = await axios.get('https://api.jotform.com/form/' + jotformAPI.form_id + '/submissions?apiKey=' + jotformAPI.key)
const { submissionId } = req.body
const customer = customers.data.content.find(client => client.id === submissionId)
if (!customer) throw new Error('contact does not exist')
//on the first call the error is triggered, only on the second time I get the data.
...
Related
I'm relatively new working with promises in JS. I got the API call to work on the initial homepage, but I'm having issues when I go to another page that is using the same API call.
In my api.js file I have the following:
const key = apiKey;
const commentsUrl = axios.get(`https://project-1-api.herokuapp.com/comments/?api_key=${key}`);
const showsUrl = axios.get(`https://project-1-api.herokuapp.com/showdates?api_key=${key}`);
async function getData() {
const allApis = [commentsUrl, showsUrl];
try {
const allData = await Promise.allSettled(allApis);
return allData;
} catch (error) {
console.error(error);
}
}
In my index.html
import { getData } from "./api.js";
let data = await getData(); //This works and gathers the data from the API.
In my shows.html
import { getData } from "./api.js";
let showsData = await getData(); //This does not and says that cannot access commentsUrl (api.js) before it is initialized. But it is?
If I comment out the code from "show", the API GET request works fine and the index page loads the API data correctly. Can anyone explain to me what's happening and why I would be getting the uninitialized error?
I also should note that if I split the API calls onto two seperate two js files (one for the index, one for the shows), the API calls works and displays the data as it is intended to.
On the homepage, the code is executing the 2 GET requests on page load/initialization of the JS code. When you navigate away from the homepage, presumably with some sort of client-side routing, the 2 GET requests no longer reference 2 Promises as they have already been executed.
You could instead move the GET requests into your function like:
const key = apiKey;
async function getData() {
try {
const commentsUrl = axios.get(`https://project-1- api.herokuapp.com/comments/?api_key=${key}`);
const showsUrl = axios.get(`https://project-1-api.herokuapp.com/showdates?api_key=${key}`);
const allApis = [commentsUrl, showsUrl];
const allData = await Promise.allSettled(allApis);
return allData;
} catch (error) {
console.error(error);
}
}
Can I call an imported function in app.get("/")? is it good practice?
Does this also mean that all logic must be done in the addArticle function?
post.js
const addArticle = async post => {
const postData = {
body: post.body,
title: post.title,
username: post.username
};
const { data } = await axios.post(
`${BASE_URL}/data/`,
postData
);
return data;
}
catch (err) {
console.log(err)
}
index.js
const express = require('express')
const axios = require('axios')
const postfunc = require (./post.js)
const app = express()
app.get("/", post.addArticle)
app.listen(3001)
It’s acceptable to pass your route handler however you see fit. However, your addArticle function doesn’t conform to express route handler signature.
Your function should handle two arguments, being express.Request and express.Response instances respectively.
Finally, note that returning a value from a route handler does nothing — if you want to send that value to the client, you’ll need to use res.send or similar method.
I'm trying to respond in case a fetch for an item doesn't return something from another method that doesn't have the express response. I call this method from another that if it has the express response:
const updateItem = async (req, res = response ) => {
const id = req.params.id;
const idItem = req.params.idItem;
await itemExists( id, idItem);
...
And in the itemExists() function I search for the item in mongo and if it doesn't exist I want to send it as a response but I don't know how to do it without using Express response:
const itemExists = async ( id, idItem ) => {
const item = await PettyCashItems.findOne({ _id: id, "items._id": idItem });
if (!item) {
return ......
}
}
Thanks.
As already mentioned by one of the comments, there's no way to use response object without having access to it.
However, what you want can be achieved in two ways:
1- Easy way - return a value from itemExists function and send the response according to returned value from itemExists
const updateItem = async (req, res = response ) => {
const id = req.params.id;
const idItem = req.params.idItem;
const exists = await itemExists( id, idItem);
if (exists) {
res.send('YES')
return;
}
res.send('NO');
}
2- Better way - Setup error handling for your express application so all thrown error are caught and response is sent based on the thrown error, then you can simply
class BaseHTTPException extends Error {
constructor(statusCode) {
super();
this.statusCode = statusCode;
}
}
class ItemDoesNotExistException extends BaseHTTPException {
constructor() {
super(400)
}
}
throw new ItemDoesNotExistException()
in your itemExists function.
Further reading: https://expressjs.com/en/guide/error-handling.html
I recently updated a weather dashboard project that I was working on to have a server backend, that I could just pull the API json values from said server page (in order to hide the api key I need to utilize the weather api). Since my React project requires entering a searched city value or zipcode and the API request being made requires said searched value to be submitted into the site request, I am struggling with how to get said value from the client side to the server side. Whenever I have tried to connect a callback function which can just grab the value from the React component after the user enters it, I get a React error that states
"Module not found: You attempted to import which falls outside of the project src/ directory. Relative imports outside of src/ are not supported"
My question is, how am I meant to connect this?
Below is my code for the component collecting the search value, overview.js, where the function getSearch receives the value from a child component:
import React from 'react';
import './overview.css';
import { RecentSearches } from '../Recent Searches/recentSearches';
import { Hourly } from '../Hourly/hourly';
import { Fiveday } from '../5 Day Forecast/fiveday';
export function Overview() {
// this callback function receives the searched city entered from recentSearches and applies it to fetchForecast
const getSearch = async (searchedCity) => {
console.log(searchedCity);
fetchForecast(searchedCity);
};
async function fetchForecast(city) {
var BASE_URL = `localhost:8000/forecast`;
const response = await fetch(BASE_URL);
const data = await response.json();
// collects all of the current weather info for your search
const currentTempInfo = {
city: data.location.name,
state: data.location.region,
epochDate: data.location.localtime_epoch,
message: data.current.condition.text,
wicon: data.current.condition.icon,
currentTemp: data.current.temp_f,
currentHighTemp: data.forecast.forecastday[0].day.maxtemp_f,
currentLowTemp: data.forecast.forecastday[0].day.mintemp_f,
feelsLike: data.current.feelslike_f,
humidity: data.current.humidity,
rainLevel: data.current.precip_in,
// hourlyTemps is an array, starts from midnight(index 0) and goes every hour until 11 pm(index 23)
hourlyTemps: data.forecast.forecastday[0].hour.map((entry) => {
let epochTime, temp;
[epochTime, temp] = [entry.time_epoch, entry.temp_f];
return [epochTime, temp];
})
};
// console.log(currentTempInfo);
const daycardInfo = [];
// this for loop triggers and creates an array with all necessary values for the
function daycardForLoop() {
for (let x=0; x < 3; x++) {
const fcDayDates = data.forecast.forecastday[x].date_epoch;
const dayInfo = data.forecast.forecastday[x].day;
const dayValues = {
dates: fcDayDates,
message: dayInfo.condition.text,
wicon: dayInfo.condition.icon,
maxTemp: dayInfo.maxtemp_f,
minTemp: dayInfo.mintemp_f,
avgTemp: dayInfo.avgtemp_f
};
// pushes dayValues object into daycardInfor array
daycardInfo.push(dayValues);
};
};
daycardForLoop();
// this updates the state with the forecast for the city entered
const newData = {
currentTempInfo: currentTempInfo,
daycardInfo: daycardInfo
};
// this spits out the newly created forecast object
return newData;
};
return (
<div>
<div className='jumbotron' id='heading-title'>
<h1>Welcome to <strong>Weathered</strong>!</h1>
<h3>A Simple Weather Dashboard </h3>
</div>
<div className='container-fluid' id='homepage-skeleton'>
<div className='d-flex' id='center-page'>
<RecentSearches getSearch={getSearch}/>
<Hourly />
</div>
</div>
<Fiveday />
</div>
)
};
Here is my server code, index.js, where you can see that I need to fill in the params value of "q" that you can find in the get request for the "/forecast" page:
const PORT = 8000;
const express = require('express');
const cors = require('cors');
const axios = require('axios');
require('dotenv').config();
const app = express();
// this callback function receives the searched city entered from recentSearches and applies it to fetchForecast
// update: this callback function now passes the search to the backend for the url search to parse the new data
// export const getSearch = async (searchedCity) => {
// fetchForecast(searchedCity);
// };
app.get('/', (req, res) => {
res.json('hi');
});
app.get('/forecast', (req, res) => {
const options = {
method: 'GET',
url: `http://api.weatherapi.com/v1/forecast.json?key=${process.env.REACT_APP_API_KEY}`,
params: {
q: "*** I need to get the search value here ***",
days: 3,
api: "no",
alerts: "no"
}
};
axios.request(options).then((response) => {
res.json(response.data);
}).catch((error) => {
console.log(error);
});
});
app.listen(PORT, () => console.log(`Server running on PORT ${PORT} `))
Apologies if the message itself is convoluted. Any help/tips/comments are much appreciated!
I need to display some data in my component, unfortunately the first call to my API returns just part of the information I want to display, plus some IDs. I need another call on those IDs to retrieve other meaningful data. The first call is wrapped in a useEffect() React.js function:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
'/myapi/' + auth.authState.id
);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
And returns an array of objects, each object representing an appointment for a given Employee, as follows:
[
{
"appointmentID": 1,
"employeeID": 1,
"customerID": 1,
"appointmentTime": "11:30",
"confirmed": true
},
... many more appointments
]
Now I would like to retrieve information about the customer as well, like name, telephone number etc. I tried setting up another method like getData() that would return the piece of information I needed as I looped through the various appointment to display them as rows of a table, but I learned the hard way that functions called in the render methods should not have any side-effects. What is the best approach to make another API call, replacing each "customerID" with an object that stores the ID of the customer + other data?
[Below the approach I've tried, returns an [Object Promise]]
const AppointmentElements = () => {
//Loop through each Appointment to create a single row
var output = Object.values(data).map((i) =>
<Appointment
key={i['appointmentID'].toString()}
employee={i["employeeID"]} //returned a [Object premise]
customer={getEmployeeData((i['doctorID']))} //return a [Object Promise]
time={index['appointmentTime']}
confirmed = {i['confirmed']}
/>
);
return output;
};
As you yourself mentioned functions called in the render methods should not have any side-effects, you shouldn't be calling the getEmployeeData function inside render.
What you can do is, inside the same useEffect and same getData where you are calling the first api, call the second api as well, nested within the first api call and put the complete data in a state variable. Then inside the render method, loop through this complete data instead of the data just from the first api.
Let me know if you need help in calling the second api in getData, I would help you with the code.
Update (added the code)
Your useEffect should look something like:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get('/myapi/' + auth.authState.id);
const updatedData = data.map(value => {
const { data } = await fetchContext.authAxios.get('/mySecondApi/?customerId=' + value.customerID);
// please make necessary changes to the api call
return {
...value, // de-structuring
customerID: data
// as you asked customer data should replace the customerID field
}
}
);
setData(updatedData); // this data would contain the other details of customer in it's customerID field, along with all other fields returned by your first api call
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
This is assuming that you have an api which accepts only one customer ID at a time.
If you have a better api which accepts a list of customer IDs, then the above code can be modified to:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get('/myapi/' + auth.authState.id);
const customerIdList = data.map(value => value.customerID);
// this fetches list of all customer details in one go
const customersDetails = (await fetchContext.authAxios.post('/mySecondApi/', {customerIdList})).data;
// please make necessary changes to the api call
const updatedData = data.map(value => {
// filtering the particular customer's detail and updating the data from first api call
const customerDetails = customersDetails.filter(c => c.customerID === value.customerID)[0];
return {
...value, // de-structuring
customerID: customerDetails
// as you asked customer data should replace the customerID field
}
}
);
setData(updatedData); // this data would contain the other details of customer in it's customerID field, along with all other fields returned by your first api call
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
This will reduce the number of network calls and generally preferred way, if your api supports this.