I'm trying to upload multiple images from react-native to firebase. The way I go about this is by using expo image picker multiple - by monstrodev ( see snack.io here )
I've managed to get it to work, and managed to be able to choose multiple photos from the 'improvised' camera roll. They load fine in the add screen, but I cannot find a solution on how to upload them properly.
export default function Add(props) {
const [name, setName] = useState("");
const [photos, setPhotos] = useState(null);
const uploadImage = async () => {
const uri = photos[0].uri; // -> uri is like this because 'media' is an array with objects inside that contain name, type, and uri each, and I only need uri of each oject.
const childPath = `data/${firebase.auth().currentUser.uid}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const upload = firebase
.storage()
.ref()
.child(childPath)
.put(blob);
const uploadProgress = snapshot => {
console.log(`transferred: ${snapshot.bytesTransferred}`)
};
const uploadComplete = () => {
upload.snapshot.ref.getDownloadURL().then((snapshot) =>{
addPost(snapshot);
console.log(snapshot);
})
};
const uploadError = snapshot => {
console.log(snapshot);
};
upload.on("state_changed", uploadProgress, uploadError, uploadComplete );
};
const addPost = (downloadURL) => {
firebase.firestore()
.collection("allPosts")
.collection(firebase.auth().currentUser.uid)
.collection('userPosts')
.add({
downloadURL,
name,
}).then((function () {
props.navigation.popToTop()
}))
}
useEffect(() => {
const {params} = props.route;
if(params) {
const {photos} = params;
if(photos) setPhotos(photos)
delete params.photos
}
}, [{photos}]) // This useEffect updates when coming back from the ImageBrowserScreen (can be found in snack.io, it's exactly the same)
The main problem is, let's say, I choose 3 photos. If I console.log(photos) I get this:
Array [
Object {
"name": "name1.JPG",
"type": "image/jpg",
"uri": "file:///name1.jpg",
},
Object {
"name": "name2.JPG",
"type": "image/jpg",
"uri": "file:///name2.jpg",
},
Object {
"name": "name3.JPG",
"type": "image/jpg",
"uri": "file:///name3.jpg",
},
]
The only I could get it to work was this, give exact path to uri (photos[0].uri for example) otherwise get network error. But this only uploads the first object/photo. I also tried to map through the photos state and return all uri's into a single array and use that as const uri, but that obviously didn't work, for uri needs only one string. So I somehow need to run that function for each uri to be able to get a downloadURL and store each of them.
EDIT:
const uploadImage = async (photo) => {
const uri = photo.uri;
const childPath = `data/${
firebase.auth().currentUser.uid
}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const snapshot = await firebase.storage().ref().child(childPath).put(blob);
const downloadURL = await snapshot.ref.getDownloadURL();
imgs.push(downloadURL)
};
const uploadPhotos = async () => {
await Promise.all(photos.map(p=>uploadImage(photo)).then(addPost())
};
Can you try it with a loop trough all photos and upload them separately. Idealy using a Promise.all to upload them in parallel:
const addPost = async (downloadURLs) => {
await firebase.firestore()
.collection("allPosts")
.collection(firebase.auth().currentUser.uid)
.collection('userPosts')
.add({
downloadURLs,
name,
})
props.navigation.popToTop()
}
const uploadImage = async (photo) => {
const uri = photo.uri;
const childPath = `data/${
firebase.auth().currentUser.uid
}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const snapshot = await firebase.storage().ref().child(childPath).put(blob);
const downloadURL = await snapshot.ref.getDownloadURL();
return downloadURL
};
const uploadPhotos = async () => {
const downloadURLs=await Promise.all(photos.map(p=>uploadImage(photo))
await addPost(downloadURLs);
};
Related
I use Next JS in my project. I want do only one request in page. And in next docs says that using get Static Props is that's what I need. But in doesnt work for me.
This is my code
export async function getStaticPaths() {
const leaguesCol = await collection(database, 'leagues');
const leaguesSnapshot = await getDocs(leaguesCol);
const leagues = leaguesSnapshot.docs.map(doc => doc.data());
return {
paths: leagues.map((item) => ({
params: {
id: item.link,
},
})),
fallback: false,
};
}
export async function getStaticProps() {
const { id } = context.params;
const leaguesRef = await collection(database, 'highlights');
const q = query(leaguesRef, where('league', '==', id));
const leagueSnapshot = await getDocs(q);
const data = leagueSnapshot.docs.map(doc => doc.data());
return {
props: { data },
};
}
But, when i deploy project in Firebase, i see that request happens in every routing to page. For example, in screen my routing between "spain" and "germany" pages
enter image description here
I am hosting a react app in aws amplify using the aws-serverless version of express as the REST API, which sits inside of a lambda function. A big problem that I am facing is that asynchronous jobs in aws-serverless express cause the lambda function to complete before the promises resolve. Leaving me with no data and no error handling. This caused me to bring a lot of the asynchronous work to the front end of the application.
The problem here is that I need to bring a large amount of data into state. Right now, I am using a delay workaround (shown below) but instead need a programatic way to make sure state is finished updating before being used in the second useEffect hook (dependent on odds & failedTries props) instead of using the delay functionality.
Any help would be greatly appreciated.
const App = ({ signOut }) => {
const [odds, setOdds] = useState([]);
const [updateTime,setUpdateTime] = useState(0);
const [failedTries,setFailedTries] = useState(0);
useEffect(() => {
const setNflOdds = async () => {
let response = await updateNflOdds();
let data = response;
setOdds(data);
};
setNflOdds();
setUpdateTime(1);
const interval = setInterval(() => {
setNflOdds();
setUpdateTime(updateTime => updateTime +1);
}, 100000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
const s3Push = (() => {
if(!odds.length) {
setFailedTries(failedTries => failedTries + 1);
} else {
const delay = ms => new Promise(res => setTimeout(res, ms));
const nflOddsRefDelay = async() => {
*//This is the current workaround, wait ten seconds before pushing odds state up to the s3 bucket*
await delay(10000);
oddsS3Helper(odds);
};
nflOddsRefDelay()
}
});
s3Push();
}, [odds, failedTries]);
With the above indicated delay workaround this works for my use case (13k records inside of the array) but the data size is highly variable and I want to figure out a way that no matter the data size it brings the entire call up to the s3 bucket.
below is the content of the functions being called in the useEffect hook
const pushToS3 = async ( file, key ) => {
const creds = await Auth.currentCredentials()
const REGION = {region};
const s3Client = new S3Client({
credentials: Auth.essentialCredentials(creds),
region: REGION
});
const params = {
Bucket: {s3 bucket name}
Key: key,
Body: file,
};
s3Client.send(new PutObjectCommand(params));
console.log("file is sent");
};
const oddsS3Helper = (async (odds) => {
console.log("inside s3 helper: ",odds);
let csv = '';
let headers = Object.keys(odds[0]).join(',');
let values = odds.map(odd => Object.values(odd).join(',')).join('\n');
csv += headers + '\n' + values;
const buffedFile = csv;
const key = 'nflprops.csv'
const delay = ms => new Promise(res => setTimeout(res, ms));
const propRefDelay = async() => {
await delay(5000);
await postNflOdds();
};
pushToS3( buffedFile, key );
await propRefDelay();
});
async function getNflGames() {
const apiName = {name of serverless API inside of lambda};
const path = {path name};
const init = {
headers: {} // OPTIONAL
};
const data = await API.get(apiName, path, init);
return data;
};
async function getNflOdds(gameId) {
const apiName = {name of serverless API inside of lambda};
const path = {path name};
const init = {
headers: {}, // OPTIONAL
body: { gameId }
};
const data = await API.post(apiName, path, init);
return data;
};
async function updateNflOdds() {
const ojNflGames = await getNflGames();
const nflGameProps = [];
const nflOddsPush = ( async () => {
try {
await ojNflGames.data.map( async (game) => {
const ojNflOdds = await getNflOdds(game.id)
await ojNflOdds.data[0].odds.map((line) => {
nflGameProps.push(
{
gameId: game.id,
oddsId: line.id,
sports_book_name: line.sports_book_name,
name: line.name,
price: line.price,
checked_date: line.checked_date,
bet_points: line.bet_points,
is_main: line.is_main,
is_live: line.is_live,
market_name: line.market_name,
home_rotation_number: line.home_rotation_number,
away_rotation_number: line.away_rotation_number,
deep_link_url: line.deep_link_url,
player_id: line.player_id,
}
);
});
});
} catch (err) {
console.log("there was an error", err);
}
});
try {
await nflOddsPush();
} catch(err) {
console.log("odds push errored: ", err);
}
console.log("inside of updateNflOdds function: ",nflGameProps);
return nflGameProps;
};
I am getting unhandled promise rejection maybe because of ref is not passing please review this if possible. I'll be thankful to you.
[Unhandled promise rejection: TypeError: undefined is not a function (near '...ref.on...')]
In upload file code is distributed based on their functionality:
import firebase from '../../firebase'
const getLocalPath = async (uri)=>{
const response = await fetch(uri);
const blob = await response.blob();
return blob
}
const upload = async (blob, className,acc)=>{
const ref = await firebase.storage().ref(`/${className}`).child(`/${acc}`).put(blob)
console.log('trigger')
console.log('ref', ref)
return ref
};
const getLink = async (ref, className, acc)=>{
await ref.on(
"state_changed",
(snapshot) => {},
(err) => {
console.log(err);
},
() => {
storage
.ref(`/${className}`)
.child(`/${acc}`)
.getDownloadURL()
.then(async (url) => {
// setURL(url)
console.log(url)
return url
});
}
);
}
export {getLink, getLocalPath}
export default upload
This is where I use it:
const Publish= async ()=>{
setLoading(true)
console.log(id)
var count = 1
const blob = await getLocalPath(images[0])
const ref = await upload(blob, `serviceImages/${id}`, `${count}`, setURL)
const uri = await getLink(ref, `serviceImages/${id}`, `${count}` )
console.log(uri)
setLoading(false)
}
firebase.storage.Reference#put returns a firebase.storage.UploadTask. This UploadTask class does have a UploadTask#on() method, BUT, because you used await, you actually get a UploadTaskSnapshot object instead which is the result of UploadTask#then().
So to return the UploadTask instead of the UploadTaskSnapshot, omit the await here.
const upload = (blob, className, acc) => {
return firebase.storage()
.ref(`/${className}`)
.child(`/${acc}`)
.put(blob)
};
But if you want to wait for the upload, just await the UploadTask when you consume the code.
const blob = await getLocalPath(images[0])
const uploadTask = upload(blob, `serviceImages/${id}`, `${count}`, setURL); // note no await here
const snapshot = await uploadTask; // wait for upload to finish
const uri = await getLink(uploadTask, `serviceImages/${id}`, `${count}`)
You could also do this:
const blob = await getLocalPath(images[0])
const uploadTask = upload(blob, `serviceImages/${id}`, `${count}`, setURL);
const [snapshot, uri] = await Promise.all([
uploadTask,
getLink(uploadTask, `serviceImages/${id}`, `${count}`)
]);
You can also simplify getLink to just:
const getLink = async (uploadTask) => {
return uploadTask
.then((snapshot) => snapshot.ref.getDownloadURL());
}
which leads to:
const blob = await getLocalPath(images[0])
const uploadTask = upload(blob, `serviceImages/${id}`, `${count}`, setURL);
const [snapshot, uri] = await Promise.all([
uploadTask,
getLink(uploadTask)
]);
As a side note, try to avoid using await if you are just going to return the value on the next line. It just adds an unnecessary step as you can see here:
const getLocalPath = async (uri)=>{
const response = await fetch(uri);
const blob = await response.blob();
return blob
}
effectively becomes
const getLocalPath = async (uri) => {
return fetch(uri)
.then(response => response.blob())
.then(blob => blob); // <- this line does nothing interesting
}
So just write it as this instead:
const getLocalPath = async (uri) => {
const response = await fetch(uri);
return response.blob();
}
I just want to upload an image then get is URL to save to firestore because i want to save the url of that image to an object. I just want the await to wait for the upload to be finished and then to get the url.
Problem is when i try to get the url it says it doesnt exist but when i go to firebase is there.
const fileData = await fileUpload(imageHome, values.newHomeTeamName);
const url = await storage.ref(fileData).getDownloadURL();
console.log(url);
const fileUpload = async (image: File, newHomeTeamName: string) => {
const fileName = formatFileName(image.name, newHomeTeamName);
const uploadTask = storage.ref(fileName).put(image);
await uploadTask.on(
'state_changed',
snapsphot => {},
error => {
console.log(error);
}
);
return fileName;
};
Your fileUpload function looks a bit unusual to me. You're using await on the on() call, but that doesn't return a promise. What you should do instead is wait on the task itself. So something like:
const fileUpload = async (image: File, newHomeTeamName: string) => {
const fileName = formatFileName(image.name, newHomeTeamName);
const uploadTask = storage.ref(fileName).put(image);
await uploadTask;
return fileName;
}
Or a bit simpler:
const fileUpload = async (image: File, newHomeTeamName: string) => {
const fileName = formatFileName(image.name, newHomeTeamName);
await storage.ref(fileName).put(image);
return fileName;
}
If you want to handle the error, you can catch it in there too. But since all you do is log it, I'd recommend letting it escape and leave it to the runtime to log it.
This is what i did for my app and the url does get stored in the firestore
//imports
import { storage, db } from './firebase'
import firebase from 'firebase'
//states or hooks
const [caption, setCaption] = useState('')
const [image, setImage] = useState(null)
const [progress, setProgress] = useState(0)
const handleUpload=()=>{
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
'state_changed',
(snapshot)=>{
const progress = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) *100
);
setProgress(progress)
},
(error)=>{
console.log(error);
alert(error.message);
},
()=>{
storage
.ref('images')
.child(image.name)
.getDownloadURL()
.then(url =>{
db.collection('posts').add({
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
caption: caption,
imageUrl: url,
username: uploadname
});
setImage(null);
setProgress(0);
setCaption("");
})
}
)
}
The handleUpload trigress off when the upload button is clicked
I am uploading the image into Storage in Firebase and DB, but I have an issue that
TypeError: _config__WEBPACK_IMPORTED_MODULE_1__.default.firestore is not a function
This is my code in API for my data, it has just DB not have storage yet, how can I put both images into storage and get downloadUrl in DB.
const firebaseDb = firebaseApp.database();
const fireStore = firebaseApp.firestore();
/**
* Representation for creating new product and save to database
*/
export const doCreateProduct = (productData, image_url) => {
const productId = firebaseDb.ref().push().key;
return firebaseDb
.ref(`products/${productId}`)
.set(productData)
.then(() => {
return onceGetProducts();
})
.catch((error) => ({
error,
status: "failure",
}));
};
And this is my handleImageFile in form
const [imageFile, setImageFile] = useState();
console.log(imageFile);
const handleFileChange = (event) => {
console.log("file image", event);
const image = event.target.files[0]
setImageFile(imageFile => (image))
};
//onSave is a button save after input
const onSave = () => {
createProductRequest(values, imageFile);
};
Anybody here can help me with this problem, please? Thank you so much