How do I upload an array of image URLs to Firestore? - reactjs

I am using Formik to handle a form submission to create new real estate properties, which includes image upload. I have everything else working: I have a function that first uploads all of the images to storage and returns the image links (the getLinks function), and then we add those URLs to the formik values object to try to upload them all to my forestore. This is the whole function.
const getLinks = async (values) => {
const array = [];
for await (const file of rawFiles) {
const storageRef = ref(storage, `/houses/${file.name}`);
uploadBytes(storageRef, file).then((snapshot) => {
getDownloadURL(snapshot.ref).then((url) => array.push(url));
});
}
return array;
};
onSubmit: async (values) => {
getLinks(values)
.then((imageArray) => {
const newVals = { ...values, imageList: imageArray };
return newVals;
})
.then(async (newValues) => {
console.log(newValues);
const docRef = await addDoc(collection(db, "properties"), newValues);
})
.finally(() => {
setSnackAlert({
type: "success",
message: "You provided values!! Congrats!",
});
handleOpen();
})
.catch((err) => {
setSnackAlert({
type: "error",
message: "There was an error handling your request",
});
handleOpen();
});
The frustrating part of this, however, is that the console log directly before we submit the values to my firestore CORRECTLY logs the object with the new image URLs. Here is the console log (I purposely cut it off for sensativity, but this array does have two images)
But it does not send them up to firestore. Instead, this is what I get:
Any help is much appreciated!
Here is some replication code to attempt such a problem yourself. The form is not as lengthy, but should work the same. You will need your own firebase info to try and replicate it.
import React, {useState} from 'react';
import './App.css';
import {useFormik} from 'formik'
import { collection, addDoc } from "firebase/firestore";
import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
// YOU WILL NEED TO IMPORT YOUR OWN FIREBASE INFORMATION HERE FOR REFERENCE.
function App() {
const [images, setImages] = useState([])
const selectMultipleFiles = (e) => {
const raw = [];
const newImages = [];
raw.push(e.target.files);
for (let i = 0; i < raw[0].length; i++) {
newImages.push(URL.createObjectURL(raw[0][i]));
}
setImages(newImages);
};
const imageDisplay = images.map((image) => {
return <img src={image} style={{height: "50px", aspectRatio: "16 / 9"}}/>
})
const formik = useFormik({
initialValues: {
address: "",
price: null,
},
onSubmit: async (values) => {
getLinks(values)
.then((imageArray) => {
const newVals = { ...values, imageList: imageArray };
return newVals;
})
.then(async (newValues) => {
console.log(newValues);
const docRef = await addDoc(collection(db, "properties"), newValues);
})
.finally(() => {
alert("You provided values!! Congrats!")
});
})
.catch((err) => {
alert("Sorry, there was an error.")
});
},
})
return (
<main>
<h1>StackOvervlow Replication</h1>
<form onSubmit={formik.handleSubmit}>
<input
type="text"
name="address"
value={formik.values.address}
onChange={formik.handleChange}
/>
<input
type="number"
name="price"
value={formik.values.price}
onChange={formik.handleChange}
/>
<label htmlFor="raised-button-file">
<input
accept="image/*"
type="file"
multiple
onChange={selectMultipleFiles}
/>
</label>
<button type="submit">Submit</button>
{imageDisplay}
</form>
</main>
);
}
export default App;

The trick here is that getDownloadURL() and uploadBytes() is an async function that happens to return a promise (as stated in the documentation). You can use await() to make the code execute more synchronously. See code below:
const getLinks = async (values) => {
const array = [];
for await (const file of rawFiles) {
const storageRef = ref(storage, `/houses/${file.name}`);
const upload = await uploadBytes(storageRef, file);
const imageUrl = await getDownloadURL(storageRef);
array.push(imageUrl);
}
return array;
};
The above code will return an array instead of a promise.

I think you want to use arrayUnion()
const newVals = {...values, imageList: arrayUnion(...imageArray)}
FieldValue arrayUnion and Cloud FireStore with Flutter

Related

How to refresh the list of items after submitting a form ReactJS

Hello I am developing a todo list app using reactjs with axios. I managed to view, and add data to the database, my problem now is that I dont know how to load the updated data after submitting the form.
This is the code for fetching all the data from the database. The file name is FetchData.js
import { useEffect, useState} from 'react';
import axios from 'axios';
const FetchData = () => {
const [data, setData] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const { data: response } = await axios.get('http://localhost/todolistci/backend/index.php/todos/view', { crossDomain: true });
setData(response);
} catch (error) {
console.error(error);
}
setLoading(false);
};
fetchData();
}, []);
return {
data,
loading,
};
};
export default FetchData;
This is how I view the list of items came from FetchData.js. The file name is List.js
import React from 'react';
import ListItem from './ListItem'
import FetchData from './FetchData';
function List() {
const {
data,
loading,
} = FetchData();
return (
<ul>
{loading && <div>Loading</div>}
{!loading && (
<>
{data.map(item => (<ListItem key={item.id} id={item.id} name={item.name} complete={item.complete} />))}
</>
)}
</ul>
)
}
export default List
Now this is the form That I am submitting. File name is FormToDo.js
import React, {useState} from 'react';
import axios from 'axios';
function FormToDo() {
const [formValue, setformValue] = useState({
name: '',
});
const handleSubmit = async(e) => {
e.preventDefault();
// store the states in the form data
const nameFormData = new FormData();
nameFormData.append("name", formValue.name)
try {
// make axios post request
const response = await axios({
method: "post",
url: "http://localhost/todolistci/backend/index.php/create",
data: nameFormData,
headers: { "Content-Type": "multipart/form-data" },
});
} catch(error) {
console.log(error)
}
//empty the text field
setformValue({name: ''});
//I need to update the list of data in here
}
const handleChange = (event) => {
setformValue({
...formValue,
[event.target.name]: event.target.value
});
}
return (
<div>
<form onSubmit={handleSubmit}>
<input type="text" name="name" id="name" required placeholder="Enter To Do"
value={formValue.name} onChange={handleChange} onKeyDown={handleChange} />
<button type="submit">+</button>
</form>
</div>
)
}
export default FormToDo
This is the image of the todo app I am making.
enter image description here
Please help me. Thank you.
Your example doesn't describe how you are going back to the list after axios posted the data and got a response.
What you need is to mutate after database is updated.
one way could be to move "fetchData" from useEffect to "FetchData" and add a a mutate function that fetches the data and is made available in the return
const FetchData = () => {
const [data, setData] = useState({});
const [loading, setLoading] = useState(true);
const fetchData = async () => {
try {
const { data: response } = await axios.get(
"http://localhost/todolistci/backend/index.php/todos/view",
{ crossDomain: true }
);
setData(response);
} catch (error) {
console.error(error);
}
setLoading(false);
};
const mutate = () => fetchData();
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
mutate,
};
};
and then call mutate after data is posted.
A second solution could be to push the browser to the list page and make sure fetchData runs.
A third solution (and the solution I would choose) is to use for example SWR - React Hooks for Data Fetching that would help you to fetch & mutate data, you can see axios example in their docs

How to pass data from parent to child (react Modal)?

I have a page users.jsx (parent) and a component DialogEditUser.jsx (child) and i would like to pass a specific data of a user that is located in parent to child by it's id (using find method)
This passed data should be loaded to its input in react modal as a value.
users.jsx Code:
import React, { useState, useEffect } from 'react'
import DialogAddUser from 'src/components/DialogAddUser'
import { getUsers} from 'src/Service/api'
const Typography = () => {
const [users, setUsers] = useState([])
useEffect(() => {
getAllUsers()
}, [])
const deleteUserData = async (id) => {
setConfirmDialog({
...setConfirmDialog,
isOpen: false,
})
await deleteUser(id)
getAllUsers()
setNotify({
isOpen: true,
message: 'Article Deleted Successfully.',
type: 'error',
})
}
const getAllUsers = async () => {
let response = await getUsers()
setUsers(response.data)
console.log(response.data)
}
return ( //... )
DialogEditUsers.jsx Code:
import { useEffect, useState } from 'react'
import { getUsers, editUser } from '../Service/api'
const initialValue = {
id: '',
code: '',
article: '',
price: '',
vat: '',
status: '',
company_id: '',
}
export default function DialogAddUser() {
const [user, setUser] = useState(initialValue)
const { code, article, price, vat, status, company_id } = user
const normalize = (v) => ({
code: v.code,
article: v.article,
price: Number(v.price),
vat: Number(v.vat),
status: Number(v.status),
company_id: Number(v.company_id),
})
useEffect(() => {
loadUserDetails()
}, [])
const loadUserDetails = async () => {
const response = await getUsers(id)
console.log('loading user details ', response)
setUser(response.data.find((x) => x.id == id))
}
const editUserDetails = async () => {
const response = await editUser(id, normalize(user))
console.log('Edit user details ', response)
}
const onValueChange = (e) => {
console.log(e.target.value)
setUser({ ...user, [e.target.name]: e.target.value })
}
return (
<>
<CModal
visible={visible}
onClose={() => setVisible(false)}
backdrop={'static'}
keyboard={false}
portal={false}
>
<CModalHeader>
<CModalTitle>Edit Article:</CModalTitle>
</CModalHeader>
<CModalBody>
<CForm>
<CFormInput
type="text"
id="exampleFormControlInput1"
label="Code :"
placeholder="Enter Code"
text=" "
aria-describedby="exampleFormControlInputHelpInline"
onChange={(e) => onValueChange(e)}
value={code}
name="code"
/>
<CFormInput
type="text"
id="exampleFormControlInput2"
label="Article :"
placeholder="Enter Article"
text=" "
aria-describedby="exampleFormControlInputHelpInline"
onChange={(e) => onValueChange(e)}
value={article}
name="article"
/>
//...the rest of inputs...
api.js Code:
import axios from 'axios'
const baseURL = 'https://api.factarni.tn/article'
const token =
'eyJhbGciOiJSUzI1NiIsImtpZCI6IjIxZTZjMGM2YjRlMzA5NTI0N2MwNjgwMDAwZTFiNDMxODIzODZkNTAiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiZmFraHJpIGtyYWllbSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BSXRidm1uMS12dWJJcHNxTURKMkNTcDhVcTlmU3I1LUI1T3Y3RHY2SFRNMT1zMTMzNyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9mYWN0YXJuaSIsImF1ZCI6ImZhY3Rhcm5pIiwiYXV0aF90aW1lIjoxNjYzNzY3ODk5LCJ1c2VyX2lkIjoiaWhqM0JWM0hIRFhpVnUwdmpzV3ZidjMyRDdMMiIsInN1YiI6ImloajNCVjNISERYaVZ1MHZqc1d2YnYzMkQ3TDIiLCJpYXQiOjE2NjM3Njc4OTksImV4cCI6MTY2Mzc3MTQ5OSwiZW1haWwiOiJmYWtocmlpLmtyYWllbUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnb29nbGUuY29tIjpbIjEwODU1MTA3MjAwODIwNjMxMjI0NCJdLCJlbWFpbCI6WyJmYWtocmlpLmtyYWllbUBnbWFpbC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJnb29nbGUuY29tIn19.bvRTxHfPtJrQjF2BjXqhs7ji738kma55LMFVRb8jkeraWP-JRBi-LRPa0d7OR_-BPwCGuRBXIb6980_PP8wjhBeDdB5B77GujiGn3nUvpPOFeIaM0L7muw1NKo4YCtS3v6ifuywypTbL3_5x3SBFZEH-QV0sp5DAzaA-P3Fn8AwP66o3cUPHGengGpZNsfkJ0FYcqzH-xpyKVVWV'
//i dont mind sharing this token, it's for you to test this code if you need.
const config = { headers: { Authorization: `Bearer ${token}` } }
export const getUsers = async (id) => {
id = id || ''
try {
return await axios.get(`${baseURL}`, config)
} catch (error) {
console.log('Error while calling getArticles api ', error)
}
}
export const editUser = async (id, user) => {
return await axios.put(`${baseURL}/${id}`, user, config)
}
The only node error i'm getting in terminal using this code above (because i dont know how to pass the proper id of specified user) is:
src\components\DialogEditUser.jsx
Line 45:37: 'id' is not defined no-undef
Line 47:47: 'id' is not defined no-undef
Line 51:37: 'id' is not defined no-undef
For better explanation the problem (i dont know how to use online snippets sorry):
So what i'm expecting is: When i click on Edit button, i should get a modal with form that are filled with user data (code, article, price, vat, status and company_id) in each input of the form as value, just like this gif below:
Also, console.log(response.data) in users page shows this:
few days back i also faced the same issue. Solution for me is to create state in parent component and pass state to child. Example for it-
Parent Class
const parent= ()=>{
const [name, setName]= useState('')
const [password, setPassword]= useState('')
return(
<Child setName={setName} setPassword={setPassword} />
)
}
Child Class
const Child = ({setPassword,setName})=>{
return(
<div>
<input type="text" placeholder="Enter Name" onChange={(e)=>setPassword(e.target.value)} />
<input type="text" placeholder="Enter Name" onChange={(e)=>setPassword(e.target.value)} />
</div>
)
}
Hope my answer will help you to solve your problem, if you still facing issue, lemme know i will help you.
In users.jsx, pass props of (user.id):
<DialogEditArticle props={user.id} />
Then, in DialogEditArticle.jsx, create a new data and call in it props:
const DialogEditArticle = (data) => {
console.log(data.props)
Now console.dev, you will get all the ids of user in database (because button edit is inside map function).
Result:

Upload Images to Firestore then Set Formik Field Value

I am uploading files to Firestore using this custom upload hook I created:
import { storage } from "../firebase/config"
import { ref, getDownloadURL, uploadBytes } from "firebase/storage"
export const useUpload = () => {
const upload = async (folder, file) => {
const fileToUpload = ref(storage, `images/${folder}/${file.lastModified + file.name}`)
const url = await uploadBytes(fileToUpload, file).then(async (snapshot) => {
const imgURL = await getDownloadURL(snapshot.ref)
return imgURL
})
return url;
}
return { upload }
}
I imported the useUpload hook in another file and I'm using it like this:
const {upload} = useUpload()
const previewFiles = (items) => {
items.map(async (file) => {
setIsLoading(true)
await upload('propertyImages', file).then(url => {
return Object.assign(file, { preview: url })
})
setIsLoading(false)
})
return items.map(({preview}) => preview)
}
I use dropzone to process the files so that the users can preview the uploaded files thus:
const { getRootProps, getInputProps } = useDropzone({
accept: {
'image/*': []
},
onDrop: acceptedFiles => {
setFiles(previewFiles(acceptedFiles))
}
});
Then with this useEffect hook, I set the formik field value:
useEffect(() => {
formik.setFieldValue("images", files.map(file => (file)))
return () => files.forEach(file => URL.revokeObjectURL(file));
}, [files]);
Here's the Formik function:
const formik = useFormik({
enableReinitialize: true,
initialValues: {
images: []
},
validationSchema: Yup.object({
images: Yup.array().min(1, "Please upload at least one image").required()
}),
onSubmit: values => {
console.log(values);
}
})
The problem now is that when I submit the Formik form, the images are returning undefined. I tried to return the whole file, instead of returning only the image urls, and they work properly. Here's what I tried:
//To Upload the files
const previewFiles = (items) => {
items.map(async (file) => {
setIsLoading(true)
await upload('propertyImages', file).then(url => {
return Object.assign(file, { preview: url })
})
setIsLoading(false)
})
return items
}
//To Set the formik values
useEffect(() => {
formik.setFieldValue("images", files.map(file => (file)))
return () => files.forEach(file => URL.revokeObjectURL(file.preview));
}, [files]);
This returns the File object with the preview but if I use this useEffect, I get undefined:
useEffect(() => {
formik.setFieldValue("images", files.map(file => (file.preview)))
return () => files.forEach(file => URL.revokeObjectURL(file));
}, [files]);
Please what am I doing wrong? Any help at all would be greatly appreciated as I have been on this issue for many days!
I found out that I could just create a state to hold the values of the urls and set the values as soon as the images are done uploading.
It's something I've always known, I just didn't figure it out in time. So here's the solution I used:
const [urls, setUrls] = useState([])
const previewFiles = (items) => {
items.map(async (file) => {
setIsLoading(true)
await upload('propertyImages', file).then(url => {
setUrls(prev => [...prev, url])
return Object.assign(file, { preview: url })
})
formik.setFieldValue("images", urls)
setIsLoading(false)
})
return items
}
I only modified the previewFiles function.

How to save values sent via post request in redux-toolkit async thunk

I'm making a react component which has two input fields.One have the key : type,another the key: range.The problem is that when i submit the data i dont know how to save it as an array or something,to stack more pairs of information,because i need to display a progress bar based on the information from the input field. Could you help me please?
Here is my Slice:
export const skillSlice = createSlice({
name: "skill",
initialState: {
name:'',
range:null
},
reducers: {
setSkill: (state, action) => {
console.log("action", action.payload);
state.name = action.payload?.name;
state.range = action.payload?.range;
}
}
});
export const addNewSkill = createAsyncThunk(
'skills/addNewSkill',
async (_,{rejectWithValue,dispatch}) =>{
try{
const response = await fetch('/api/skills',{
method:'POST',
headers:{
'Content-name' : 'application/json',
},
});
if(!response.ok){
throw new Error('Can\'t add skill. Server error')
}
const data = await response.json();
dispatch(setSkill(data))
}catch(error){
return rejectWithValue(error.message);
}
}
)
export const fetchSkills = createAsyncThunk(
'skills/fetchSkills',
async (_, {rejectWithValue}) => {
try{
const response = await fetch('/api/skills',{
method:'GET',
})
// console.log(response)
if(!response.ok){
throw new Error ('Server Error!');
}
const data = await response.json();
// console.log(data)
return data;
} catch(error){
return rejectWithValue(error.message);
}
}
);
const { setSkill } = skillSlice.actions;
export const selectSkill = (state) => state?.skill;
export default skillSlice.reducer;
And here is the component:
import React, { useState,useEffect } from 'react'
import { Formik, Form, useFormik } from 'formik'
import * as Yup from 'yup'
import FormikControl from '../Form/FormikControl'
import DisplayFormikState from '../Form/DisplayFormikState.js'
import { useDispatch, useSelector } from 'react-redux'
import { addNewSkill,fetchSkills,selectSkill } from '../../features/skills/skillSlice'
const Skills = () =>{
const dispatch = useDispatch();
const [skill, setSkills] = useState({
name: '',
range: null
});
useEffect(()=>{
dispatch(fetchSkills());
},[dispatch])
const userInfo = useSelector(selectSkill);
const skillList = useSelector(state => state.skillState)
const { status, error } = useSelector(state => state.skillState)
const handleChange = (e) => {
const { name, value } = e.target;
setSkills({ ...skill, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
dispatch(addNewSkill(skill));
};
const formik = useFormik({
// initialValues:{
// name: skill.name,
// range: skill.range
// },
validationSchema:Yup.object({
}),
})
return(
<>
<section id="skills">
<h1 className='SkillSection'>Skills</h1>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="type">Type</label>
<input
id='type'
name='name'
type='text'
placeholder='Enter skill name'
onChange={handleChange}
// value={formik.values.name}
/>
</div>
<div>
<label htmlFor="level">Level</label>
<input
id='level'
type='text'
name='range'
placeholder='Enter range'
onChange={handleChange}
// value={formik.values.range}
/>
</div>
<button type='submit'>Submit</button>
</form>
</section>
</>
)
}
export default Skills
In the above code the initial state isn't an array because when i tried to push values to it i got undefined,so,i left the working state not to get confused. Thanks in advance!

Api marvel response don't show react

I try to training in react and want to make a form who call the api marvel when submitted with the current input and display the name + description of the character search.
The Api call is ok but when i submit the form nothing show any advice?
import React, { Component, useEffect, useState } from 'react'
import axios from 'axios'
const SearchEngine = React.forwardRef((props, ref) => {
const [asked, setAsked] = useState([]);
const [characterInfos, setCharacterInfos] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const [loading, setLoading] = useState(true);
const [inputs, setInputs] = useState('');
const handleChange = (event) => {
setInputs(event.target.value);
console.log(inputs);
}
const getCharacters = (inputs) => {
setSearchTerm(inputs)
axios
.get(`https://gateway.marvel.com:443/v1/public/characters?name=${searchTerm}&apikey=XXX`)
.then(response => {
console.log(searchTerm)
console.log(response)
setCharacterInfos(response.data.data.results[0]);
setLoading(false);
console.log(response.data.data.results[0].name)
response.data.data.results.map((item) => {
return characterInfos.push(item.name)
})
localStorage.setItem(characterInfos, JSON.stringify(response.data))
if (!localStorage.getItem('marvelStorageDate')) {
localStorage.setItem('marvelStorageDate', Date.now());
}
})
.catch(error => {
console.log(error);
})
}
return (
<div className="search-container">
<h1>Character Infos</h1>
<form onSubmit={getCharacters}>
<input
type="text"
placeholder="Search"
value={inputs}
onChange={handleChange}
/>
<input type="submit" value="Envoyer" />
</form>
<ul>
<li>{characterInfos.name}</li>
</ul>
</div>
)
})
export default React.memo(SearchEngine)
Thanks for your help. Any to advice to show a list of all the character and make a search filter who work with minimum 3 characters?
getCharacters is fired with form submit event as param. You are assuming that is getting inputs from the state wrongly:
const getCharacters = event => {
event.preventDefault() // Prevent browser making undesired form native requests
// setSearchTerm(inputs); // Not sure what are you trying here but, again, inputs is a form submit event
axios
.get( // use searchValue as query string in the url
`https://gateway.marvel.com:443/v1/public/characters?name=${searchValue}&apikey=XXX`
)
.then(response => {
console.log(searchTerm);
console.log(response);
setCharacterInfos(response.data.data.results[0]);
setLoading(false);
console.log(response.data.data.results[0].name);
response.data.data.results.map(item => {
return characterInfos.push(item.name);
});
localStorage.setItem(characterInfos, JSON.stringify(response.data));
if (!localStorage.getItem("marvelStorageDate")) {
localStorage.setItem("marvelStorageDate", Date.now());
}
})
.catch(error => {
console.log(error);
});
};

Resources