results.map is not a function - node.js

I am close to displaying my axios response results to a table but i keep getting the error. Still learning React. See the error below
Below is the code making API call
const listJobs = () =>{
axios.get( `${process.env.REACT_APP_API}/search-projects`,
{params: {category
}
})
.then(response => {
console.log('LOG SUCCESS --', response.data);
const results = response.data;
setValues({...values, results: results});
console.log('My State Data', results);
})
}
This is to display the data on the table
const { results } = values;
{ results.map(({id, title, description, budget}, index ) => {
return (
<tr key={id}>
<td>{id}</td>
<td>{title}</td>
<td>{description}</td>
<td>{budget}</td>
</tr>
);
})}

Axios makes an asynchronous request: you need to call it from useEffect hook (or componentDidMount) like so:
useEffect(() => {
axios.get(`${process.env.REACT_APP_API}/search-projects`).then(res => console.log(res))
})
Other problem is you aren't checking if the request finished and the state is updated
{
results &&
results.map(({ id, title, description, budget }, index) => {
return (
<tr key={id}>
<td>{id}</td>
<td>{title}</td>
<td>{description}</td>
<td>{budget}</td>
</tr>
)
})
}
And finally, the request must be an array to use Array.prototype.map() function.

My guess is that the issue here is that, until your async axios call returns, results will be undefined, and so on the first few attempts to render your component, it is trying to call .map() on undefinded which won't work.
You could add some kind of guard in your jsx like:
{ results && results.map(...) }
Or initialize your results to an empty array at first, and reassign to the results of your axios call once that resolves.

const results = [];
if( Array.isArray( response.data ) ){
setValues({...values, results: results});
};

Related

React useState hook is running indefinitely - infinite loop

I have a simple react functional component.
The code should be self-explanatory. If the status is equal to 'some status', go to a URL, fetch some information, and set the state of the component to the information that has been fetched. On the return () just display the information. Everything works fine, the id of the data is displayed. However, when I open the dev tools and do the inspection, the console.log("data"+data.id); is run indefinitely. I wonder what is making it run indefinitely.
if I remove the change data(data) from inside the fetch, the console.log does not run indefinitely.
I am scratching my head as to why, changing the status would make the code enter in an infinite loop?
function ReturnInfo(props) {
var currentstatus = props.currentstatus; // receiving the current status as prop from parent.
const [data, changeData] = useState({});
let { slug } = useParams(); // getting the slug.
if (currentstatus == 'some status') {
fetch(`https:someurl/${slug}`).
then(res => res.json()).
then(data => {
console.log("data" + data.id);
changeData(data);
});
return (
<div>
<div>
{data.id}
</div>
</div>
)
}
else {
return (
<p>try harder!</p>
)
}
}
You should use useEffect, The Effect Hook lets you perform side effects in function components:
useEffect docs = https://reactjs.org/docs/hooks-effect.html
And if you don't add the dependency array it will run on each update.
Simple wrap your side-effect/async task code within the useEffect function.
And add the dependency array. add empty array if you want to run it only once.
useEffect(() => {
fetch(`https:someurl/${slug}`).
then(res => res.json()).
then(data => {
console.log("data" + data.id);
changeData(data);
});
}, [slug])
It will stop the unnecessary rerender.
Edit Try this
function ReturnInfo(props) {
var currentstatus = props.currentstatus; // receiving the current status as prop from parent.
const [data, changeData] = useState({});
let { slug } = useParams(); // getting the slug.
useEffect(() => {
fetch(`https:someurl/${slug}`).
then(res => res.json()).
then(data => {
console.log("data" + data.id);
changeData(data);
});
}, [slug])
if (currentstatus === 'some status') {
return (
<div>
<div>
{data.id}
</div>
</div>
)
}
return <p>try harder!</p>
}
You are calling the function every time the component is rendered. The function gets called, it updates the state, and makes the component to re-render.
You should call the function when an event occurrs or change the currentstatus value every time the block is executed.
changeData(data) will cause reevaluation of the component. Which leads to call fetch again, which make infinite loop.
useEffect(() {
if (currentstatus == 'some status') {
fetch(`https:someurl/${slug}`).
then(res => res.json()).
then(data => {
console.log("data" + data.id);
changeData(data);
});
}},[])

Express returns empty array

I currently have the following code
router.get('/uri', (request,response) => {
let final = [];
TP.find({userID: request.userID})
.then(tests =>{
tests.forEach(test => {
A.findById(test.assignmentID)
.then(assignment => {
final.push({
testID: test._id,
name: assignment.title,
successRate: `${test.passedTests}/${test.totalTests}`
})
})
.catch(error => {
console.log(error)
})
})
return response.send(final);
})
.catch(err => {
console.log(err);
return response.sendStatus(500);
})
})
The code is supposed to query 2 MongoDB databases and construct an array of objects with specific information which will be sent to the client.
However, I always get an empty array when I call that endpoint.
I have tried making the functions async and make them wait for results of the nested functions but without success - still an empty array.
Any suggestions are appreciated!
forEach doesn't care about promises inside it. Either use for..of loop or change it to promise.all. The above code can be simplified as
router.get('/uri', async (request,response) => {
const tests = await TP.find({userID: request.userID});
const final = await Promise.all(tests.map(async test => {
const assignment = await A.findById(test.assignmentID);
return {
testID: test._id,
name: assignment.title,
successRate: `${test.passedTests}/${test.totalTests}`
};
}));
return response.send(final);
});
Hope this helps.

How to returned poll data after each nodejs api call to reactjs component

I need to poll the data until the response.Status === 'UpdatesComplete'.
I have written this node js API function which basically polls the data -
const getResults = async (location) => {
try {
const response = await poll(location);
if (response.Status && response.Status === 'UpdatesComplete') {
return response;
}
return await getResults(location);
} catch (err) {
throw err;
}
};
app.get('/url', async (req, res) => {
try {
const results = await getResults(req.query);
res.json(formatData(results));
} catch (err) {
res.status(500).send(err);
console.error(err);
}
});
I am calling this API from ReactJS class component inside ComponentDidMount lifecycle method -
componentDidMount = () => {
axios.get('url', {
params: params
})
.then(response => {
console.log(response.data, 'data');
// setting the data on state
this.setState({ filteredList: response.data });
})
.catch(err => {
this.setState({ error: true });
});
};
This is working fine. But since the API is returning data till all the data has been fetched(after doing polling), it's taking a very long time on view to load the data. I am basically looking for returning the polling data to view as soon as the data fetched and simultaneously polling the API until all the data has been fetched. In this case, data will keep on updating after each polling on the view which will improve the user experience.
Thanks in advance.
You are finding the lazy loading or infinite scroll solution on the server-side. There is no simple way to do this.
The basic idea of the solution is to paginate your result with polling. ie.
call url?size=2&offset=0 from the client-side. Then on the server-side just poll first 2 results and return. next time call url?size=2&offset=2 and so-on.

How to use Promises correctly with multiple requests

I am using twitter's API to receive recent tweets by querying specific hash tags. The first GET request is to search/tweets which takes the hashtag and others queries in it's parameters. The response for this returns an array of tweets (objects). I push each of these id's into an external array. Now I want to loop through this array and make another call to statuses/show for each of these IDs so that I can get the location data of the user posting the tweet. The statuses/show end-point only takes a single tweet id but I have an array. How would I go about using the same getRequest function for an array of IDs?
I tried to implement it by reading online about Promises but it's not working.
Here's my code:
function getRequest(url, params) {
return new Promise(function (success, failure) {
twitterSearch.get(url, params, function (error, body, response) {
if (!error) {
success(body);
} else {
failure(error);
}
});
});
}
app.post('/twitter', (req, res) => {
console.log("body", req.body);
var tweetIDs = [];
var bounds = [];
getRequest('search/tweets', {q: req.body.tag, result_type:'recent', count:'100'})
.then((tweets) => {
tweets.statuses.map((status) => {
if(status.user.geo_enabled) {
console.log("ID: ", status.id);
tweetIDs.push(status.id);
}
});
return getRequest('statuses/show', tweetIDs); //I know tweetIDs is wrong, dont know what to do from here.
}).then((data) => {
console.log("User data for the ID")
console.log(data);
}).catch((error) => {
console.log(error)
});
res.send(bounds);
bounds.length = 0;
});
You nearly got it right! Map all tweets to Promises returned by your getRequest() function. Then, the Promise.all() method will return you a single Promise that resolves when all of the promises in your array resolves.
By the way, you can use Array.reduce instead of map to both filter and map your array at the same time, this factor your code a little better.
getRequest('search/tweets', {q: req.body.tag, result_type:'recent', count:'100'})
.then( tweets => {
let requests = tweets.statuses.reduce( (ret,status) => {
if(status.user.geo_enabled)
// The following is still wrong though, I guess. Format the params of getRequest accordingly.
ret.push( getRequest('statuses/show', status.id) );
return ret;
}, []);
return Promise.all(requests);
})
.then( results => {
results.forEach( res => {
console.log("User data for the ID");
console.log(res);
})
})
EDIT : Related jsfiddle
Replace the line
return getRequest('statuses/show', tweetIDs); //I know tweetIDs is wrong, dont know what to do from here.
with
return Promisel.all( tweetIDs.map( (tId) => getRequest('statuses/show', tId) );

Axios.all, how to configure axios wait time to mitigate hung up?

My application uses an internal webservice for fetching data, i have a job which creates approx 500 requests which getsfired async to complete the fetch operation.
I make use of Axios, by creating an array of axios promises and then resolving them using using Axios.all();
It works fine until some 200 requests but post that i get socket hung up, however on the server side i see the requests are being processed.
How to configure axios to set custom time out, or is it a better idea to splice my promises array and then run them as multiple batches ?
Source code
let getAxiosPromiseArray = (urlList) => {
var axiosArrayofPromise = [];
return new Promise ( (resolve, reject) => {
try {
urlList.forEach ( (URL) => {
axiosArrayofPromise.push(axios.get(URL));
});
resolve(axiosArrayofPromise);
}
catch (err) {
reject("There is a problem getting Axios array of promises " + err);
}
})
}
async function processAxiosPromises (PromiseArray) {
try {
var results = []
results = await axios.all(PromiseArray);
return results;
}
catch(err) {
throw("There was a problem resolving promises array (Axios) " + err);
}
}
getallID().then ( (urlList) => {
return getAxiosPromiseArray(urlList);
}).then( (AxiosPromises) => {
return processAxiosPromises(AxiosPromises);
}).then ((resultData) => {
console.log(resultData);
});
Error
There was a problem resolving promises array (Axios) Error: socket hang up
First, that pair of functions getAxiosPromiseArray() and processAxiosPromises() needs fixing.
Your new Promise() construction is unnecessary. You can simply return Promise.all(arrayofPromise) (or axios.all(...) if you must) and do away with the other function.
Renaming the remaining function to something meaningful, you would end up with eg :
let getData = (urlList) => {
return Promise.all(urlList.map(URL => axios.get(URL)))
.catch(error => {
error.message = "There is a problem getting Axios array of promises " + error.message; // augment the error message ...
throw error; // ... and re-throw the errror.
});
};
And call as follows :
getallID().then(getData)
.then(resultData => {
console.log(resultData);
}).catch(error => {
console.error(error);
});
That will put you on solid ground but, on its own, is unlikely to fix a concurrency problem (if that's what it is), for which the simplest approach is to use Bluebird's Promise.map with the concurrency option.
The caller code can remain the same, just change getData(), as follows:
let getData = (urlList) => {
let concurrency = 10; // play with this value to find a reliable concurrency limit
return Promise.map(urlList, URL => axios.get(URL), {'concurrency': concurrency})
.catch(error => {
error.message = "There is a problem getting Axios array of promises " + error.message;
throw error;
});
};
// where `Promise` is Bluebird.
const axios = require('axios');
const axiosThrottle = require('axios-throttle');
//pass axios object and value of the delay between requests in ms
axiosThrottle.init(axios,200)
const options = {
method: 'GET',
};
const urlList = [
'https://jsonplaceholder.typicode.com/todos/1',
'https://jsonplaceholder.typicode.com/todos/2',
'https://jsonplaceholder.typicode.com/todos/3',
'https://jsonplaceholder.typicode.com/todos/4',
'https://jsonplaceholder.typicode.com/todos/5',
'https://jsonplaceholder.typicode.com/todos/6',
'https://jsonplaceholder.typicode.com/todos/7',
'https://jsonplaceholder.typicode.com/todos/8',
'https://jsonplaceholder.typicode.com/todos/9',
'https://jsonplaceholder.typicode.com/todos/10'
];
const promises = [];
const responseInterceptor = response => {
console.log(response.data);
return response;
};
//add interceptor to work with each response seperately when it is resolved
axios.interceptors.response.use(responseInterceptor, error => {
return Promise.reject(error);
});
for (let index = 0; index < urlList.length; index++) {
options.url = urlList[index];
promises.push(axiosThrottle.getRequestPromise(options, index));
}
//run when all promises are resolved
axios.all(promises).then(responses => {
console.log(responses.length);
});
https://github.com/arekgotfryd/axios-throttle

Resources