I am new to React and javascript for that matter and am having trouble setting a components state. What I am trying to do is fetch a list of photos from an endpoint of mine. Once I have the photo list I need to map over the photos and call another endpoint to add some other meta data. My goal is to set the state of my component array once so I only render the webpage one time with all of the images.
I believe I need a Promise.All statement to determine when all of the images have returned from a fetch statement inside of a map. However I am not sure if this is the right approach or how it would look.
I will post some of my code below. Any and all recomendations are welcome, Thanks!
server.js
const express = require('express');
const app = express();
var AWS = require('aws-sdk');
var url = require('url');
var http = require('http');
var s3 = new AWS.S3({apiVersion: '2006-03-01'});
var url = require('url');
var http = require('http');
var sizeOf = require('image-size');
var params = {
Bucket: "owenpersonalphotos",
};
const URL = "http://owenpersonalphotos.s3.amazonaws.com/";
app.get('/api/listPhotos', (req,res) => {
console.log("GET /api/listPhotos")
s3.listObjects(params, function (err, data) {
res.json(data.Contents)
});
});
app.get('/api/getPhoto/:key', (req, res) => {
console.log("GET /api/getPhoto/"+req.params.key)
let key = req.params.key;
s3.listObjects(params, function (err, data) {
let imgUrl = URL+key;
options = url.parse(imgUrl);
http.get(options, function (response) {
var chunks = [];
response.on('data', function (chunk) {
chunks.push(chunk);
}).on('end', function() {
var buffer = Buffer.concat(chunks);
res.json(sizeOf(buffer))
});
})
});
});
const port = 5000;
app.listen(port, () => console.log(`Server started on port ${port}`));
App.js
class App extends React.Component {
constructor() {
super();
this.loadPhotos = this.loadPhotos.bind(this);
this.state = {
Photos: []
};
}
componentDidMount() {
this.loadPhotos();
}
loadPhotos() {
let p = []
fetch('api/listPhotos')
.then(res => res.json())
.then(data =>
{ Promise.all(data.map(data => fetch("api/getPhoto/" + data.Key)
.then(res => res.json())
.then(getPhotoData => (p = ({
src:"https://owenpersonalphotos.s3.amazonaws.com/"+data.Key,
width: getPhotoData.width,
height : getPhotoData.height,
id: data.ETag
},this.setState({Photos:p}))))))
})
}
render() {
return (
<div className="App">
{this.state.Photos}
</div>
);
}
}
You could try something along the lines of this. Unfortunately I am unable to replicate your exact scenario so it might not work, but here's how you could resolve a Promise.all.
const initialiser = fetch('api/listPhotos')
.then(res => res.json())
.then(data => { // map every photo request to the promise of the fetch
let requests = data.map(data => fetch("api/getPhoto/" + data.Key)
.then(res => res.json())
.then(getPhotoData => ({
src: "https://owenpersonalphotos.s3.amazonaws.com/" + data.Key,
width: getPhotoData.width,
height : getPhotoData.height,
id: data.ETag
)}))
// Promise.all waits until all jobs are resolved
Promise.all(requests)
.then(responses => this.setState({ Photos: responses });
})
})
I would use React Hooks instead for functional components and storing state into an array
import React, { useState, useEffect } from "react";
State set as empty array below:
const [photos, setPhotos] = useState([]);
This following effect will run getPhotos() once on page load
useEffect(() => {
getPhotos();
}, []);
getPhotos from API and map to state photos
const getPhotos = async () => {
//async way
try {
const response = await fetch("YOUR API URL");
let photos = await response.json;
setPhotos(photos);
} catch (err) {
console.log(err);
}
/* with just promises
return fetch("YOUR API URL")
.then(response => response.json())
.then(response => {
console.log(response);
setPhotos(response);
});
*/
and then you can just map it in the return statement:
<div className="photos-container">
{photos.map((p, i) => {
return (
<div className="photo" key={i}>
<img src={photos.imgSrc} alt={photos.name}>
</div>
);
})}
</div>
Hope this helps I know it's not your example exactly
Your fetches were look like a promise hell situation, I will make it a bit clearer like this:
loadPhotos() {
let promises = [];
fetch('api/listPhotos')
.then(res => res.json())
.then(list => {
for (let photo of list) {
const aCall = fetch("api/getPhoto/" + photo.Key).then(res => res.json());
promises.push(aCall);
}
return Promise.all(promises);
}).then(detailedPhotos => {
const Photos = detailedPhotos.map(item => ({
src: "https://owenpersonalphotos.s3.amazonaws.com/" + item.Key,
width: item.width,
height : item.height,
id: item.ETag
}));
this.setState({ Photos });
})
}
render() {
return (
<div className="App">
{this.state.Photos.map(p => <div>{p.id}</div>)}
</div>
);
}
Related
I am currently working with a clone of a streaming platform, it turns out that this clone has the TMDB API integrated and I want to remove it to store the objects returned by this api in a firebase database, but I am a little confused.
In my Firebase file, I have a promise that returns an array of objects and it looks like this:
export const getGamesDocument = () => {
return new Promise((resolve, reject) => {
const documents = [];
firestore
.collection("games")
.get()
.then((snapshot) => {
snapshot.forEach((doc) => {
const documentData = doc.data();
documentData.id = doc.id;
documents.push(documentData);
});
resolve(documents);
})
.catch((error) => {
reject(error);
});
});
};
So far everything is going well where I am getting confused is in this redux code since I have no knowledge of the subject:
export const fetchAdventureMoviesRequest = () => ({
type: moviesActionTypes.FETCH_ADVENTURE_MOVIES_REQUEST,
});
export const fetchAdventureMoviesSuccess = (adventureMovies, isPage) => ({
type: isPage
? moviesActionTypes.FETCH_ADVENTURE_MOVIES_SUCCESS
: moviesActionTypes.LOAD_MORE_ADVENTURE_MOVIES_SUCCESS,
payload: adventureMovies,
});
export const fetchAdventureMoviesFailure = error => ({
type: moviesActionTypes.FETCH_ADVENTURE_MOVIES_FAILURE,
payload: error,
});
export const fetchAdventureMoviesAsync = (fetchUrl, isPage) => {
return dispatch => {
dispatch(fetchAdventureMoviesRequest());
axios
.get(fetchUrl)
.then(res => {
const adventureMovies = res.data.results.map(el => ({
...el,
isFavourite: false,
}));
if (isPage) {
dispatch(fetchAdventureMoviesSuccess(adventureMovies, isPage));
} else dispatch(fetchAdventureMoviesSuccess(adventureMovies));
})
.catch(error => {
const errorMessage = error.message;
dispatch(fetchAdventureMoviesFailure(errorMessage));
});
};
};
I want to remove the array of objects that are obtained in the constant "adventureMovies" and replace it with the array of objects that I obtain in the aforementioned promise.
Client/App.js:
import React, { useState, useEffect } from "react";
import Axios from "axios";
const App = () => {
const [movieName, setmovieName] = useState("");
const [movieReview, setmovieReview] = useState("");
const [getReview, setgetReview] = useState([]);
useEffect(() => {
Axios.get("http://localhost:3001/api/get", (result) => {
console.log(result.data);
setgetReview(result.data);
});
}, []);
const submitReview = () => {
Axios.post("http://localhost:3001/api/insert", {
movieName: movieName,
movieReview: movieReview
})
.then(() => {
alert("Success");
})
.catch((e) => alert(e));
};
return (
<div className="index">
<h2>movie name</h2>{" "}
<input type="text" onChange={(e) => setmovieName(e.target.value)} />
<h2>movie rating</h2>{" "}
<input type="text" onChange={(e) => setmovieReview(e.target.value)} />
<button onClick={submitReview}>submit</button>
{getReview.map((val) => {
return (
<h1>
Movie name : {val.movieName} Movie review: {val.movieReview}
</h1>
);
})}
</div>
);
};
export default App;
Server/index.js:
const express = require("express");
const mysql = require("mysql");
const cors = require("cors");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(express.json());
const db = mysql.createConnection({
host: "localhost",
root: "root",
password: "",
database: "crudatabase"
});
db.connect((err) => {
if (err) throw err;
});
app.get("/api/get", (req, res) => {
const selectStmt = "SELECT movieName,movieReview FROM movie_review;";
db.query(selectStmt, (err, result) => {
res.send(result);
});
});
app.post("/api/insert", (req, res) => {
const movieName = req.body.movieName;
const movieReview = req.body.movieReview;
const insertStmt =
"INSERT INTO movie_review (movieName,movieReview) VALUES (?,?);";
db.query(insertStmt, [movieName, movieReview], (err, result) => {
console.log(err);
});
});
app.listen(3001, () => {
console.log("Server running on 3001");
});
In the above react and express code I am able to insert the data in the database but after inserting then() part in client is not working. Also the useEffect is not working. I tried many ways but not able to get the reason. I would be glad if someone can solve me the error and all the dependencies are already installed.
In your useEffect, you're passing a callback to Axios.get - this is not consistent with the Axios API (you even do it correctly in the submitReview function!):
useEffect(() => {
// change to Promise.then() chain
Axios.get("http://localhost:3001/api/get").then((result) => {
console.log(result.data);
setgetReview(result.data);
});
}, []);
Your then() chain is not working because your POST response handler never returns a status or a response! Just like in your GET handler, your POST handler needs to let the client know that a request has been successful. e.g. res.send(/*...*/) or even just res.sendStatus(200).
As you are dealing with the promise and have used the thenable syntax while submitting the values but you are not using it while getting the values. try using the below code and check whether this resolves your problem. One more concise method to deal with promises is to use async/await try to use the below code hopes this resolves your problem.
useEffect(() => {
const getMovies = async () => {
try {
let { data } = await Axios.get("http://localhost:3001/api/get");
console.log(data);
setgetReview(data);
} catch (error) {
console.log(error);
}
};
getMovies();
}, []);
Your useEffect is returning a promise try to use async await or .then on your code.
Try to change it from:
useEffect(() => {
Axios.get("http://localhost:3001/api/get", (result) => {
console.log(result.data);
setgetReview(result.data);
});
}, []);
To:
useEffect(() => {
const get_data = async () => {
try {
const result = await Axios.get("http://localhost:3001/api/get")
console.log(result.data)
setgetReview(result.data)
} catch (e) {
console.log(e)
}
}
get_data()
}, []);
export const FETCH_DB_BEGIN = 'FETCH_DB_BEGIN'
export const FETCH_DB_SUCCESS = 'FETCH_DB_SUCCESS'
export const FETCH_DB_FAILURE = 'FETCH_DB_FAILURE'
export const fetchDatabase = () => {
return dispatch => {
const profile_url = 'localhost:5000/profiles'
const release_url = 'localhost:5000/releases'
const emp_url = 'localhost:5000/users'
let promises = []
let options = {
headers: header,
method: 'get',
mode: 'cors',
body: null,
}
dispatch(fetchDbBegin());
// run the script async. change state when it's done.
let profile_promise = new Promise((resolve, reject) => {
fetch(profile_url, options)
.then(res => res.json())
.then(resText => {
// Use Dispatch Here?
})
}).catch(err => {
console.log(err)
})
promises.push(profile_promise)
// run the script async. change state when it's done.
let release_promise = new Promise((resolve, reject) => {
fetch(release_url, options)
.then(res => res.json())
.then(resText => {
})
}).catch(err => {
console.log(err)
})
promises.push(release_promise)
// run the script async. change state when it's done.
let emp_promise = new Promise((resolve, reject) => {
fetch(emp_url, options)
.then(res => res.json())
.then(resText => {
})
}).catch(err => {
console.log(err)
})
promises.push(emp_promise)
Promise.all(promises).then(values => {
console.log(values)
})
}
}
export const fetchDbBegin = () => ({
type: FETCH_DB_BEGIN
});
export const fetchDbSuccess = (data) => ({
type: FETCH_DB_SUCCESS,
payload: { data }
});
export const fetchDbFailure = (err) => ({
type: FETCH_DB_FAILURE,
payload: { err }
});
I am in a process of refactoring a React class component to use Redux. It initially had all API calls inside the componentDidMount and it was so messy.
I am using redux-thunk to move this out from the class component.
The fetchDatabase in my databaseAction.js does everything that componentDidMount did in the class component.
Normally if it was a single API call, I would have just dispatched the fetchDbSuccess as the API call was done successfully. However, using Promise.All which takes three async API calls, I am not sure whether I should
create a separate action for each API call (fetchProfileSuccess, fetchReleaseSuccess, and fetchUserSuccess) and dispatch each one of them at the end of each Promise (the place where I put //Use Dispatch Here? in the code.
OR
Just dispatch single fetchDbSuccess when the Promise.all gets resolved.
If I choose to do 2, am I supposed to update all three states in my reducer?
Thanks
You should only dispatch and update state if you have code that cares about said state updates. For example, if you're just wanting to show a single spinner then have the spinner go away when fully completed, your user doesn't necessarily care about each atomic operation, so you don't need it reflected in state. If you have a UI that does show each, then you would want those extra dispatches.
By the way, your Promises look a bit overcomplicated. If you decide you don't need those extra state changes, you can simplify to this:
export const FETCH_DB_BEGIN = 'FETCH_DB_BEGIN'
export const FETCH_DB_SUCCESS = 'FETCH_DB_SUCCESS'
export const FETCH_DB_FAILURE = 'FETCH_DB_FAILURE'
export const fetchDatabase = () => {
return dispatch => {
dispatch(fetchDbBegin());
const urls = [
'http://localhost:5000/profiles',
'http://localhost:5000/releases',
'http://localhost:5000/users'
];
const options = {
headers: header,
method: 'get',
mode: 'cors',
body: null,
}
const fetchJson = url => fetch(url, options).then(res => res.json());
Promise.all(urls.map(fetchJson))
.then(([profile, release, employee]) => {
dispatch(fetchDbSuccess({ profile, release, employee }));
})
.catch(err => {
dispatch(fetchDbFailure(err));
});
}
}
export const fetchDbBegin = () => ({
type: FETCH_DB_BEGIN
});
export const fetchDbSuccess = (data) => ({
type: FETCH_DB_SUCCESS,
payload: { data }
});
export const fetchDbFailure = (err) => ({
type: FETCH_DB_FAILURE,
payload: { err }
});
I'm using a react frontend and fetching data from my node server. I feel like my code looks a bit redundant, is there a better way to refactor all this?
App.js
searchStock = async (value) => {
let priceURL = `/stock/${ value }/price`
// fetch price data
fetch(priceURL)
.then(res => {
if (res.ok) {
res.json()
.then( (result) => {
this.setState({
price: result
})
})
}
else {
console.log("Something went wrong...")
}
})
}
server.js
app.get('/stock/:symbol/price', (req, res) => {
const token = 'abcde123'
const symbol = req.params.symbol
const apiURL = `https://sandbox.iexapis.com/stable/stock/${symbol}/price?token=T${token}`
fetch(apiURL)
.then(response => {
console.log(response.status)
if (response.ok) {
response.json().then((data) => {
res.json(data)
});
}
else {
res.sendStatus(response.status)
}
})
.catch(error => {
console.log(error);
});
})
As these two code segments live in different apps (frontend and backend) I don't think there's a pretty way of DRYing this.
Introduce library file with fetching logic
src/helper.js
exports.fetchHelper = (url) => fetch(url)
.then(response => {
if (response.ok) {
return response.json();
} else {
res.sendStatus(response.status)
}
})
.catch(console.error);
and use respectively
app.js
import { fetchHelper } from 'src/helper'; // or whatever else your bundler setup requires
searchStock = async (value) => {
const priceURL = `/stock/${ value }/price`;
await fetchHelper(priceURL).then((result) => {
this.setState({
price: result
})
})
}
server.js
const fetchHelper = require('src/helper').fetchHelper;
app.get('/stock/:symbol/price', (req, res) => {
const token = 'abcde123'
const symbol = req.params.symbol
const apiURL = `https://sandbox.iexapis.com/stable/stock/${symbol}/price?token=T${token}`
fetchHelper(apiURL).then((response) => {
res.json(data);
})
Or something similar...
I make a request to the server via a map with different urls, then I set the data in State and use it for output. I want the requests to be consecutive but sometimes they do not work correctly and get bugs, how to write the code for normal data retrieval?
const urlList = ["countries", "states", "cities", "users"];
componentDidMount() {
urlList.map( (url, index) => {
return servicesAPI.getResourse(url).then( (body) => {
index !== 3 ? this.setState({
dataAPI : [...this.state.dataAPI, body] }) :
this.setState({
dataAPI : [...this.state.dataAPI, body],
loaded: true
})
})
})
export default class ServicesAPI {
_apiBase = `http://localhost:3001/`;
async getResourse(url) {
const res = await fetch(`${this._apiBase}${url}`);
if (!res.ok) {
throw new Error(`Could not fetch ${url}` +
`, received ${res.status}`)
}
return await res.json();
}
Use of Promise.all();
componentDidMount() {
const fetchPromises = [];
urlList.forEach( (url, index) => {
fetchPromises.push(servicesAPI.getResourse(url));
});
const allResourcesPromise = Promise.all(fetchPromises);
allResourcesPromise.then(data => {
// list with responses
}).catch(err => {
console.log(err.toString());
});
}
Sample example:
https://jsbin.com/vevufumano/1/edit?html,js,console,output
Also instead of then, where is possible, you can use async/await for more cleaner code.