React JS fetch data from multiple API function - reactjs

I was tasked to create a front-end for a poc. I am not a front end developer but I chose to use React JS.
There is only one page fetching data from multiple API endpoints. API endpoints return a simple json object.
I managed to get that to work however my code is ugly af and I want to create a function to handle all of that but I can't seem to get it right. Here's my code
export default class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
group1: [],
group2: [],
group3: [],
isLoaded: false,
}
}
componentDidMount() {
const group1_url = "http://localhost/api/1"
const group2_url = "http://localhost/api/2"
const group3_url = "http://localhost/api/3"
fetch(group1_url)
.then(res => res.json())
.then(json => {
this.setState({
group1: json,
})
});
fetch(group2_url)
.then(res => res.json())
.then(json => {
this.setState({
group2: json,
})
});
fetch(group3_url)
.then(res => res.json())
.then(json => {
this.setState({
group3: json,
})
});
}
I am trying to create a function like this:
function fetch_data(url, state) {
fetch(url)
.then(res => res.json())
.then(json => {
this.setState({
state: json,
})
});
}
var group1 = fetch_data(group1_url, group1);
So far no joy. How can I create a function to fetch data and set a state in js?
Alternatively how can I make my code look better? Or is there something else I should use/look into?

Pass a string as the second parameter, and use a computed property:
function fetch_data(url, state) {
fetch(url)
.then(res => res.json())
.then(json => {
this.setState({
[state]: json,
})
});
}
fetch_data(group1_url, 'group1');
I'd also highly recommend catching errors - possible unhandled rejections should be avoided whenever possible.
You might want to use Promise.all to wait for all groups to load:
const dataSources = {
group1: 'http://localhost/api/1',
group2: 'http://localhost/api/2',
group3: 'http://localhost/api/3',
};
Promise.all(
Object.entries(dataSources).map(([propertyName, url]) => (
fetch(url)
.then(res => res.json())
.then((result) => {
this.setState({
[propertyName]: result
})
})
))
)
.then(() => {
this.setState({ isLoaded: true })
})
.catch((error) => {
// handle errors
})
(also note that your json argument is not a JSON - JSON is only a format that exists with strings. Something that has been deserialized is just a plain object or array. Better to call it something less misleading, like result as I did)

You could try Promise.all
Promise.all takes an array of promises (it technically can be any iterable, but is usually an array) and returns a new promise.
const points = [
"http://localhost/api/1",
"http://localhost/api/2",
"http://localhost/api/3",
];
const responses = await Promise.all(points.map((point) => fetch(point)));
const data = await Promise.all(responses.map((response) => response.json()));
const [group1, group2, group3] = data;
this.setState({
group1,
group2,
group3,
});
Just remember to wrap this logic in an async function

You can do something like this.
function fetch_data(url, state) {
fetch(url)
.then(res => res.json())
.then(json => {
this.setState({
[state]: json,
})
});
}
var group1 = fetch_data(group1_url, 'group1');

Related

Using this.setstate with Fetch API

I'm trying to use a fetch call to retrieve some API data. Unfortunately I cannot use this to set the state.
consructor() {
this.state = {
weather: ""
};
this.search = this.search.bind(this);
}
search(postalCode) {
const url = `https://api.weatherbit.io/v2.0/forecast/daily?&postal_code=${postalCode}&key=${API_KEY}&days=7`;
fetch(url)
.then(response => response.json())
.then(data => console.log(data));
}'
This code works fine, and prints an object to the console containing the correct data.
consructor() {
this.state = {
weather: ""
};
this.search = this.search.bind(this);
}
search(postalCode) {
const url = `https://api.weatherbit.io/v2.0/forecast/daily?&postal_code=${postalCode}&key=${API_KEY}&days=7`;
fetch(url)
.then(response => response.json())
.then(data => this.setState({weather: data}))
.catch(error => console.log(error));
}
As soon as I try to use the data to update my state, I get the "this.setState is not a function" error. I've bound the search function in the constructor.
I've even tried to bind it like this:
fetch(url)
.then(response => response.json())
.then(data => this.setState({ weather: data })).bind(this);
This doesn't work either. Anyone have any ideas?
I can't see your class definition, but I believe that using the state requires you to extend Component
The issue you are getting is that this component doesn't have the state functions in it.
Here's some code from a working project of mine using similar fetches, returned by the services:
type HomeProps = {}
type State = {
projects: ProjectModel[],
skills: SkillModel[],
resume: JobModel[]
}
export default class Home extends Component<HomeProps, State> {
private projectService: ProjectService;
constructor(props: HomeProps) {
super(props);
this.projectService = new ProjectService();
this.state = {
projects: []
}
}
componentDidMount() {
this.getProjects();
}
private getProjects() {
this.projectService.retrieveItems().then(projects => {
this.setState({projects});
})
}
Here is the service:
export default class ProjectService {
url = "http://api.flynndev.us";
async retrieveItems() {
return fetch(`${this.url}/projects/all`)
.then(response => response.json());
}
async getItem(itemLink: string) {
return fetch(`${this.url}/projects/${itemLink}`)
.then(response => response.json());
}
}
Note: I'm using Typescript. Feel free to ignore the typing stuff for regular React

Forming an array from two joined apis in react

I am attempting to create a set of joined data from two apis that I would love to implement in a table, apparently I am not getting the expected result.
The logic behind my code is :
Get the data from the first api
loop through each element in the data to get a specific data from the second api depending with the id of each element.
Create a new key to each element, each data obtained in second api as a value.
the resulting data is set in the state.
I have been able to accomplish step 1 to 3 except step 4.
class App extends React.Component{
constructor(props){
super(props);
this.state = {
Data: [],
};
}
componentDidMount() {
fetch('http://localhost:8000/tasks?format=json')
. then(res => res.json())
.then(data => data['results'].forEach(element => {
fetch(`http://localhost:8000/task/${element.id}/runs`)
.then(res => res.json())
.then(data2 => element['rundata'] = data2)
.then(this.state.Data.push(element))
}))
.catch(err => console.log(err))
}
render(){
console.log('data', this.state.Data)
return(
)
}
}
export default App;
You can only update react state with setState method.
Try the below code.
then(this.setState(prev => ({
data: [...prev, element]
})
Well the main reason for the issue is that you are not using setState correctly.
you need to use setState, and you need to not mutate the state. Always copy the old state and then modify it.
The second issue which may occur is that your forEach method is not synchronised.
Try changing your componentDidMount to this:
componentDidMount() {
fetch('http://localhost:8000/tasks?format=json')
.then(res => res.json())
.then(data => {
for (const element of data['results']) {
fetch(`http://localhost:8000/task/${element.id}/runs`)
.then(res => res.json())
.then(data2 => element['rundata'] = data2)
.then(this.setState({ Data: [...Data, element] })
}
}
.catch(err => console.log(err))
}
this.setState(prevState => ({
myArray: [...prevState.myArray, "new value"]
}))
in the other hand, i don't recommend to loop in fetch, either you use a post method and loop throw all the ids in your server side and return an array, or if you have a light database you get all the result and do a loop in your client side.
When you're setting data into the state in React you only need to use this.setState().
So for your case you only have to run this.setState({ Data: element }).
Maybe this :
componentDidMount() {
fetch('http://localhost:8000/tasks?format=json')
.then(res => res.json())
// create array of promise
.then(data => data.results.map(element => {
return fetch(`http://localhost:8000/task/${element.id}/runs`)
.then(res => element.rundata = res.json())
.cath(err => console.log(err))
}))
.then((arrayPromise) => {
Promise.all(arrayPromise)
.then(resolved => this.setState({data:resolved}))
})
.catch(err => console.log(err))
}
render(){
console.log('data', this.state.data)
return(
<></>
)
}
}

Is there a way to use template literals for fetch request?

I'd like to define a function that makes a fetch request based on its parameter so I can simplify the fetch calls inside componentDidMount(). I've tried the code below but it gives an Unexpected token error for the setState function. Is there a way to use the parameter of the function inside then()?
constructor() {
super();
// cases in date order from 1 to 5. Cases5 is the latest.
this.state = {
cases1: [],
cases2: [],
cases3: [],
cases4: [],
cases5: [],
};
}
componentDidMount() {
fetch("/cases/1")
.then((res) => res.json())
.then((cases1) => this.setState({ cases1 }));
fetch("/cases/2")
.then((res) => res.json())
.then((cases2) => this.setState({ cases2 }));
fetch("/cases/3")
.then((res) => res.json())
.then((cases3) => this.setState({ cases3 }));
fetch("/cases/4")
.then((res) => res.json())
.then((cases4) => this.setState({ cases4 }));
fetch("/cases/5")
.then((res) => res.json())
.then((cases5) => this.setState({ cases5 }));
}
fetchCaseData = (index) => {
fetch(`/cases${index}`)
.then((res) => res.json())
.then((`cases${index}`) => this.setState({ `cases${index}` }));
}
You can't simply declare a dynamic name for a variable. You can however set dynamic keys for an object using brackets notation but not object shorthand syntax
Updated code will look like below
.then((val) => this.setState({ [`cases${index}`]: val }));

How to pass query parameters to a url inside a lambda function (netlify environment)

When the user submit a search form I want to add the query parameter to the api url that is inside a lambda function.
I set up the netlify environment inside a react app and initialize a lambda function.Now I only get the response using hard-coded queries.
How can I pass parameters to the event.queryStringParameters?
exports.handler = function(event, context, callback) {
const API_PARAMS = qs.stringify(event.queryStringParameters);
const { API_TOKEN, API_URL } = process.env;
const URL = `${API_URL}search?part=snippet&maxResults=5&key=${API_TOKEN}&q=animals`;
// Let's log some stuff we already have.
console.log("Injecting token to", API_URL);
console.log("logging event.....", event);
console.log("Constructed URL is ...", URL);
console.log('params are...', API_PARAMS);
const pass = body => {
callback(null, {
statusCode: 200,
body: JSON.stringify(body)
});
};
const get = () => {
axios
.get(URL)
.then(response => {
console.log(response.data);
pass(response.data);
})
.catch(err => pass(err));
};
if (event.httpMethod == "GET") {
get();
}
};
App.js
componentDidMount() {
fetch('.netlify/functions/youtube')
.then(response => response.json())
.then(data => console.log(data));
}
this took me a minute to figure out but it's in the query string:
let thing = 123;
fetch(`/.netlify/functions/${filename}?item=${thing}`)
.then(response => response.json())
.then(json => console.log(json))
if it's a post, you will need to parse it from the query but a regular get call you can pull it out of the event.queryStringParameters

ReactJS setState when all nested Axios calls are finished

I have a problem with updating my state from nested axios call inside forEach loop:
constructor(props) {
super(props);
this.state = {
isLoaded: false,
items: []
};
//Binding fetch function to component's this
this.fetchFiles = this.fetchFiles.bind(this);
}
componentDidMount() {
this.fetchFiles();
}
fetchFiles() {
axios.get('/list')
.then((response) => {
var items = response.data.entries;
items.forEach((item, index) => {
axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
})
.catch(error => {
console.log(error);
})
});
this.setState(prevState => ({
isLoaded: true,
items: items
}));
console.log(this.state.items);
})
.catch((error) => {
console.log(error);
})
}
The idea is to get all items from Dropbox using it's API (JavaScript SDK)
and then for each item I also need to call different API endpoint to get a temporary download link and assign it as a new property. Only after all items will get their links attached I want to setState and render the component. Could somebody please help with this, I spend already multiple hours fighting with promises :S
You could use Promise.all to wait for multiple promises. Also keep in mind that setState is async and you wont see immediate changes. You need to pass a callback.
fetchFiles() {
axios.get('/list')
.then((response) => {
var items = response.data.entries;
// wait for all nested calls to finish
return Promise.all(items.map((item, index) => {
return axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
return item
});
}));
})
.then(items => this.setState(prevState => ({
isLoaded: true,
items: items
}), () => console.log(this.state.items)))
.catch((error) => {
console.log(error);
})
}
Try making the fetchfiles() function as an asynchronous method by adding the async keyword.Now, we have to wait till the items to get their download link, so add a await keyword before that line which makes the code to wait till the axios call gets completed.
async function fetchFiles() {
axios.get('/list')
.then(async function(response){
var items = response.data.entries;
await items.forEach((item, index) => {
axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
})
.catch(error => {
console.log(error);
})
});
this.setState(prevState => ({
isLoaded: true,
items: items
}));
console.log(this.state.items);
})
.catch((error) => {
console.log(error);
})
}
I haven't tested the code, but it should probably work.

Resources