Download URL from storage not transferring to firestore - React [duplicate] - reactjs

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 7 months ago.
I am trying to add the imgUrl uploaded to firestore but it's not working. The url is generated and the image is stored in firebase storage but I can't seem to pass that URL as a field into Firestore
This is my code
import React, {useEffect, useState} from "react";
import ReactTagInput from "#pathofdev/react-tag-input";
import "#pathofdev/react-tag-input/build/index.css";
import {db, storage} from "../firebase";
import { getDownloadURL, ref, uploadBytes, listAll} from "firebase/storage";
import {
addDoc,
collection,
getDoc,
serverTimestamp,
doc,
updateDoc,
} from "firebase/firestore";
import { toast } from "react-toastify";
import { v4 } from 'uuid';
import {useNavigate} from "react-router-dom";
const initialState = { // initial state for our form
title: "",
tags: [],
trending: "no",
category: "",
description: "",
imgUrl: ""
}
const categoryOption = [
"Fashion",
"Technology",
"Food",
"Politics",
"Sports",
"Business"
]
export default function AddEditBlog({ user }) {
let urlDownload
const [form, setForm] = useState(initialState)
const [imageUpload, setImageUpload] = useState()
const [imageList, setImageList] = useState([])
const navigate = useNavigate()
const imageListRef = ref(storage, "images/") // to use in useEffect (listAll), because we
// wanna access all the files inside the images folder
const { title, tags, category, trending, description, imgUrl} = form;
const uploadImage = () => {
if(imageUpload == null) return; // if there's no image, return and get out of this function
uploadBytes(imageRef, imageUpload).then((snapshot)=> {
getDownloadURL(snapshot.ref).then((url) => {
// setImageList((prev) => [...prev, url])
console.log("url", url)
toast.info("Image upload to firebase successfully");
setForm((prev) => ({ ...prev, imgUrl: url }))
console.log("form", form)
})
})
}
const handleChange = (event) => {
setForm({...form, [event.target.name]: event.target.value})
}
const handleTags = (tags) => {
setForm({ ...form, tags})
}
const handleTrending = (event) => {
setForm({ ...form, trending: event.target.value })
}
const onCategoryChange = (event) => {
setForm({ ...form, category: event.target.value })
}
const handleSubmit = async (event) => {
event.preventDefault()
if (category && tags && title && description && trending && imgUrl) {
try {
console.log("url", urlDownload)
await addDoc(collection(db, "blogs"), {
...form,
timestamp: serverTimestamp(),
author: user.displayName,
userId: user.uid
})
toast.success("Blog created successfully");
} catch(error) {
console.log(error)
}
} else {
console.log("complete form")
}
navigate("/")
}
return(
<div className="container-fluid mb-4">
<div className="container">
<div className="col-12">
<div className="text-center heading py-2">
Create Blog
</div>
</div>
<div className="row h-100 justify-content-center align-items-center">
<div className="col-10 col-md-8 col-lg-6">
<form className="row blog-form" onSubmit={handleSubmit}>
<div className="col-12 py-3">
<input
type="text"
className="form-control input-text-box"
placeholder="Title"
name="title"
value={title}
onChange={handleChange}
/>
</div>
<div className="col-12 py-3">
<ReactTagInput
tags={tags}
placeholder="Tags"
onChange={handleTags}
/>
</div>
<div className="col-12 py-3">
<p className="trending">Is it trending blog ?</p>
<div className="form-check-inline mx-2">
<input
type="radio"
className="form-check-input"
value="yes"
name="radioOption"
checked={trending === "yes"} //if trending is yes select checked button
onChange={handleTrending}
/>
<label htmlFor="radioOption" className="form-check-label">
Yes
</label>
<input
type="radio"
className="form-check-input"
value="no"
name="radioOption"
checked={trending === "no"} //if trending is not select checked button
onChange={handleTrending}
/>
<label htmlFor="radioOption" className="form-check-label">
No
</label>
</div>
</div>
<div className="col-12 py-3">
<select
value={category}
onChange={onCategoryChange}
className="catg-dropdown"
>
<option>Please select category</option>
{categoryOption.map((option, index) => (
<option value={option || ""} key={index}>
{option}
</option>
))}
</select>
</div>
<div className="col-12 py-3">
<textarea
className="form-control description-box"
placeholder="Description"
value={description}
name="description"
onChange={handleChange}
/>
</div>
<div className="mb-3">
<input
type="file"
className="form-control"
onChange={(event)=> {
setImageUpload(event.target.files[0])
}}
/>
</div>
<div className="col-12 py-3 text-center">
<button
className="btn btn-add"
type="submit"
onClick={uploadImage}
>
Submit
</button>
</div>
</form>
</div>
</div>
</div>
</div>
)
}
Everything else loads successfully. The console log I do of the url returns the correct URL so I don't know why I can't pass it into firestore

Related

React Datepicker - Uncaught RangeError: Invalid time value

Building a simple ToDo list app in ReactJS. Below is my Add Task functional component:
import React, { useState } from "react";
import TaskDataService from "../services/task.service";
import DatePicker from 'react-datepicker'
import FadeIn from 'react-fade-in';
import "react-datepicker/dist/react-datepicker.css";
import 'bootstrap/dist/css/bootstrap.min.css';
const AddTask = () => {
const initialTaskState = {
id: null,
title: "",
description: "",
completed: false,
startDate: new Date()
};
const [task, setTask] = useState(initialTaskState);
const [submitted, setSubmitted] = useState(false);
const handleInputChange = event => {
const { name, value } = event.target;
setTask({ ...task, [name]: value });
};
const saveTask = () => {
var data = {
title: task.title,
description: task.description,
startDate: task.startDate
};
TaskDataService.create(data)
.then(response => {
setTask({
id: response.data.id,
title: response.data.title,
description: response.data.description,
completed: response.data.completed,
startDate: response.data.startDate
});
setSubmitted(true);
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};
const newTask = () => {
setTask(initialTaskState);
setSubmitted(false);
};
return (
<FadeIn>
<div className="submit-form">
{submitted ? (
<div>
<h4>Task submitted successfully!</h4>
<button className="btn btn-success" onClick={newTask}>
Add
</button>
</div>
) : (
<div>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
required
value={task.title}
onChange={handleInputChange}
name="title"
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<input
type="text"
className="form-control"
id="description"
required
value={task.description}
onChange={handleInputChange}
name="description"
/>
</div>
<div className="form-group">
<label htmlFor="startDate">Start Date</label>
<DatePicker
selected={ task.startDate }
onChange={date => handleInputChange({target: {value: date.toISOString().split("T")[0], name: 'startDate'}})}
name="startDate"
dateFormat="yyyy-MM-dd"
/>
</div>
<button onClick={saveTask} className="btn btn-success">
Submit
</button>
</div>
)}
</div>
</FadeIn>
);
}
export default AddTask;
When I try to select a date from the Datepicker calendar, app rendering crashes. There are a few errors but it looks like the main one is "Uncaught RangeError: Invalid time value". However, the task is successfully added to the database and I can view it upon reloading the app. But for whatever case, it crashes upon submit.
In contrast, this is my standalone Task component which also contains code for editing an existing task. In that component, everything works 100%. I can open the Datepicker calendar on the selected task, select a new date, and submit it succesfully with zero problems:
import React, { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";
import TaskDataService from "../services/task.service";
import DatePicker from 'react-datepicker';
import FadeIn from 'react-fade-in';
const Task = props => {
const { id } = useParams();
let navigate = useNavigate();
const initialTaskState = {
id: null,
title: "",
description: "",
completed: false,
startDate: new Date(),
};
const [currentTask, setCurrentTask] = useState(initialTaskState);
const [message, setMessage] = useState("");
const getTask = id => {
TaskDataService.get(id)
.then(response => {
setCurrentTask(response.data);
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};
useEffect(() => {
if (id)
getTask(id);
}, [id]);
const handleInputChange = event => {
const { name ,value } = event.target;
setCurrentTask({ ...currentTask, [name]: value });
};
const updateCompleted = status => {
var data = {
id: currentTask.id,
title: currentTask.title,
description: currentTask.description,
completed: currentTask.completed,
startDate: currentTask.startDate
};
TaskDataService.update(currentTask.id, data)
.then(response => {
setCurrentTask({ ...currentTask, completed: status });
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};
const updateTask = () => {
TaskDataService.update(currentTask.id, currentTask)
.then(response => {
console.log(response.data);
setMessage("The task was updated successfully!");
})
.catch(e => {
console.log(e);
});
};
const deleteTask = () => {
TaskDataService.remove(currentTask.id)
.then(response => {
console.log(response.data);
navigate("/tasks");
})
.catch(e => {
console.log(e);
});
};
return (
<FadeIn>
<div>
{currentTask ? (
<div className="edit-form">
<h4>Task</h4>
<form>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
value={currentTask.title}
onChange={handleInputChange}
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<input
type="text"
className="form-control"
id="description"
value={currentTask.description}
onChange={handleInputChange}
/>
</div>
<div className="form-group">
<label>
<strong>Status:</strong>
</label>
{currentTask.completed ? "Completed" : "Pending"}
</div>
<div className="form-group">
<label htmlFor="startDate">Start Date</label>
<DatePicker
onChange={date => handleInputChange({target: {value: date.toISOString().split("T")[0], name: 'startDate'}})}
name="startDate"
dateFormat="yyyy-MM-dd"
value={currentTask.startDate.toString().split("T")[0]}
/>
</div>
</form>
{currentTask.completed ? (
<button
className="badge badge-primary mr-2"
onClick={() => updateCompleted(false)}
>
Mark Pending
</button>
) : (
<button
className="badge badge-primary mr-2"
onClick={() => updateCompleted(true)}
>
Mark Complete
</button>
)}
<button
className="badge badge-danger mr-2"
onClick={deleteTask}
>
Delete
</button>
<button
type="submit"
className="badge badge-success"
onClick={updateTask}
>
Update
</button>
<p>{message}</p>
</div>
) : (
<div>
<br />
<p>Please click on a Task...</p>
</div>
)}
</div>
</FadeIn>
);
}
export default Task;
Done a lot of testing and research but no dice. Any ideas?
Commenter Konrad Linkowski provided the clue to the solution. I removed the line "startDate: response.data.startDate" and now it all works. I do not fully understand, so if anyone wants to explain the answer then please feel free.

React error "Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak"

I have a Login component and I receive that error: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak. I googled for the error and found some people having the same issue and I tried to solve it like them, but it didn't work for me. Why I have this error, and how do I fix it? Thanks
COMPONENT LOGIN
import React, { useState, useContext, useEffect } from "react";
import { useLocation } from "wouter";
import logic from "../../logic";
import { AuthContext } from "../../context/AuthContext";
import validate from "./validateRulesLogin";
import literals from "../../common/i18n/literals";
import "./index.css";
export default function Login(language) {
const [, setAuth] = useContext(AuthContext);
const [, pushLocation] = useLocation();
const [messageError, setMessageError] = useState("");
const [errorsValidateForm, setErrorsValidateForm] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [data, setData] = useState({ email: "", password: "" });
const { lang } = language;
const { login_notRegister } = literals;
useEffect(() => {}, [errorsValidateForm, isSubmitting]);
let isCancelled = false;
async function handleOnSubmit(e) {
e.preventDefault();
if (!isSubmitting) {
let errorsForm = validate(data, lang);
if (Object.keys(errorsForm).length === 0 && !isCancelled) {
setIsSubmitting(true);
const { email, password } = data;
try {
await logic.loginUser(email, password, lang);
setAuth(true);
setIsSubmitting(false);
pushLocation("/");
} catch (error) {
setMessageError(error.message);
setIsSubmitting(false);
setTimeout(() => {
setMessageError("");
}, 3000);
}
}
setErrorsValidateForm(errorsForm);
}
return () => {
isCancelled = true;
};
}
const handleOnChangeData = (e) => {
e.preventDefault();
setData({
...data,
[e.target.name]: e.target.value,
});
};
return (
<>
{messageError && (
<div className="message">
<p className="messageError">{messageError}</p>
</div>
)}
<section className="hero is-fullwidth">
<div className="hero-body">
<div className="container">
<div className="columns is-centered">
<div className="column is-4">
<form id="form" onSubmit={(e) => handleOnSubmit(e)} noValidate>
<div className="field">
<p className="control has-icons-left">
<input
className="input"
name="email"
type="email"
placeholder="Email"
onChange={handleOnChangeData}
required
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
</p>
{errorsValidateForm.email && (
<p className="help is-danger">
{errorsValidateForm.email}
</p>
)}
</div>
<div className="field">
<p className="control has-icons-left">
<input
className="input"
name="password"
type="password"
placeholder="Password"
onChange={handleOnChangeData}
required
/>
<span className="icon is-small is-left">
<i className="fas fa-lock"></i>
</span>
</p>
{errorsValidateForm.password && (
<p className="help is-danger">
{errorsValidateForm.password}
</p>
)}
</div>
<div className="field">
<p className="control">
<button type="submit" className="button is-success">
Login
</button>
</p>
</div>
<div>
{login_notRegister[lang]}
</div>
</form>
</div>
</div>
</div>
</div>
</section>
</>
);
}

React Axios post error 400 bad request, should NOT have additional properties

I am a newby in React.js and am trying to post data from a form in React to a localhost server.
The data I retrieve is from TMDB and it consists of attributes of a movie with a certain id.
I use axios in useEffect, for the get queries and they work well.
Afterwards with useState I set the movie data.
Which I use to pre-fill in a form. It is an add movie form, which when submitted, the formData should be added to a localhost server json db.
For this I use axios post and here is where I get stuck.
Have been trying many things and nothing works.
Thank you to anyone who can help me out.
Here is the code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useHistory } from "react-router-dom";
import './AddMovie.scss';
const AddMovieForm = (props) => {
const movieID = props.movie.id;
const [actors, setActors] = useState([]);
const [similarMovies, setSimilarMovies] = useState([]);
const [movies, setMovies] = useState({
title: "",
release_date: "",
categories: [],
description: "",
poster: "",
backdrop: "",
});
/* QUERY FOR THE MOVIE CAST & SIMILAR MOVIES */
const API_KEY = process.env.REACT_APP_API_KEY;
const base_url = `https://api.themoviedb.org/3`;
useEffect(() => {
axios.get(`${base_url}/movie/${movieID}?api_key=${API_KEY}&language=en-US&page=1`)
.then((response) => {
setMovies({
id: response.data.id,
title: response.data.title,
release_date: response.data.release_date,
categories: response.data.genres,
description: response.data.overview,
poster: `https://image.tmdb.org/t/p/w342${response.data.poster_path}`,
backdrop: `https://image.tmdb.org/t/p/w342${response.data.backdrop_path}`,
});
// console.log(response.data, movieData.categories);
}).catch((error) => {
console.log(error);
});
axios.get(`${base_url}/movie/${movieID}/credits?api_key=${API_KEY}`)
.then((r) => {
// console.log(r.data.cast.slice(0, 3));
setActors(r.data.cast.slice(0, 3));
}).catch((error) => {
console.log(error);
});
axios.get(`${base_url}/movie/${movieID}/similar?api_key=${API_KEY}&language=en-US&page=1`)
.then((r) => {
// console.log(r.data.results.slice(0, 3));
setSimilarMovies(r.data.results.slice(0, 3));
}).catch((error) => {
console.log(error);
});
}, [base_url, movieID]);
const history = useHistory();
const imgUrl = "http://image.tmdb.org/t/p/w342";
const actorsData = actors.map(item => {
return ({
name: item.name,
photo: imgUrl + item.profile_path,
character: item.character
}
)
});
const similarMoviesData = similarMovies.map(item => {
return ({
title: item.title,
poster: imgUrl + item.poster_path,
release_date: item.release_date
}
)
});
// console.log(similarMoviesData);
/* FORM */
const formData = {
title: movies.title,
release_date: movies.release_date,
categories: movies.categories.map(c => c.name),
description: movies.description,
poster: movies.poster,
backdrop: movies.backdrop,
actors: actorsData,
similar_movies: similarMoviesData,
id: movies.id
};
const LOCAL_URL = "http://localhost:3000/movies";
const handleSubmit = (event) => {
event.preventDefault();
console.log(formData);
axios.post(LOCAL_URL, formData,
{
headers: {
'Content-Type': 'application/json',
}
})
.then(response => {console.log(response)})
.catch((error) => console.log(error) );
history.push('/');
// setRedirectTo('/movies');
};
return (
<div className={!props.showForm ? "form-wrapper" : "form-wrapper--active"}>
<form onSubmit={handleSubmit}>
{/* MOVIE */}
<div className="form-top-wrapper">
{/* movie title */}
<div className="title-input-wrapper">
<h4>Title</h4>
<div className="title-input">
<input
className=""
type="text"
id="title"
name="title"
value={formData.title}
placeholder=""
required
/>
</div>
</div>
{/* movie release date */}
<div className="release-date-input-wrapper">
<h4>Release Date</h4>
<div className="release-date-input">
<input
className=""
type="text"
id="release-date"
name="release_date"
value={formData.release_date}
placeholder=""
/>
</div>
</div>
</div>
<div className="form-description-wrapper">
{/* movie description */}
<div className="description-input-wrapper">
<h4>Synopsis</h4>
<textarea
className=""
rows="3"
cols="90"
id="description"
name="description"
value={formData.description}
placeholder=""
readOnly
/>
</div>
</div>
<div className="form-bottom-wrapper">
{/* movie categories */}
<div className="form-categories">
<h4>Categories</h4>
<ul className="categories-list">
<li key="">
<label htmlFor="categories">Name</label>
<input
className=""
type="text"
id="categories"
name="categories"
value={formData.categories}
placeholder=""
/>
</li>
</ul>
</div>
{/* ACTORS */}
<div className="form-actors">
<h4>Actors</h4>
<ul className="actors-list">
{actors.map(a => (
<li key={a.id}>
{/* actors name */}
<label htmlFor="actorName">Name</label>
<input
className=""
type="text"
id="actor-name"
name="actors"
value={a.name}
placeholder=""
/>
{/* actors character */}
<label htmlFor="actorCharacter">Characters</label>
<input
className=""
type="text"
id="actor-character"
name="actors"
value={a.character}
placeholder=""
/>
</li>
))}
</ul>
</div>
{/* SIMILAR MOVIES */}
<div className="form-similar-movies">
<h4>Similar Movies</h4>
<ul className="similar-movies-list">
{similarMovies.map(sm => (
<li key={sm.id}>
{/* similar movies title */}
<label htmlFor="similarMovieTitle">Title</label>
<input
className=""
type="text"
id="similar-movie-title"
name="similar_movies"
value={sm.title}
placeholder=""
/>
{/* similar movies release_date */}
<label htmlFor="similarMovieReleaseDate">Release Date</label>
<input
className=""
type="text"
id="similar-movie-release-date"
name="similar_movies"
value={sm.release_date}
placeholder=""
/>
</li>
))}
</ul>
</div>
</div>
<button className="btn btn-info" type="submit">
<span>Ajouter le film à votre bibliothéque</span>
</button>
</form>
</div>
);
};
export default AddMovieForm;

Can't send uploaded image url from firebase into another components in react hooks

I can't send uploaded image url from firebase storage into another component using react hooks , can anybody help me? I'm new to the react and working on this part of code about 2 weeks and did'nt have any progress, it really annoying me.
This is main component where images url should come in order to render it
import React, { useState, useEffect} from "react";
import firebase from "firebase";
import Uploader from "./uploader";
function EditorBlog() {
const [title, setTitle] = useState("");
const [text, setText] = useState("");
const [category, setCategory] = useState("");
const [url , setUrl]= useState("");
const [items, setItems] = useState([]);
const onSubmit = (data) => {
data.preventDefault();
setItems([...items, {title, text, category , url} ]);
firebase
.firestore()
.collection('blogContent')
.add({
title,
category,
text,
url
// file
})
.then(()=>{
setTitle('')
setCategory('')
setText('')
setUrl('')
});
};
return (
<div>
<form onSubmit={onSubmit} className="col-md-6 mx-auto mt-5">
<div className="form-group">
<label htmlFor="Title">Blog Title</label>
<input
type="text"
name="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="form-control"
id="Title"
placeholder="Arnold Schwarzneiger"
/>
</div>
<Uploader/>
<div className="form-group">
<label htmlFor="categorySelect">Select Category</label>
<select className="form-control" value={category} onChange={e => setCategory(e.target.value)} name="category" id="categorySelect">
<option>Nutrition</option>
<option>Fitness</option>
<option>Recipes</option>
<option>Succesfull Stories</option>
<option>Other</option>
</select>
</div>
<div className="form-group">
<label htmlFor="blogText">Blog Text</label>
<textarea
className="form-control"
name="text"
id="blogText"
rows="3"
value={text}
onChange={(e) => setText(e.target.value)}
></textarea>
</div>
<button
type="submit"
className="btn btn-primary offset-5"
onClick={onSubmit}
>
Save
</button>
</form>
<div className="mt-5">
{
items.map((item, index) => (
<div className="row bg-dark mx-auto ">
<div key={item.id} className="col-md-6 blogs_content mx-auto mt-5 " >
<div className="mblog_imgtop "><p>Beginner</p></div>
<img className="img-fluid editor_img " src={item.url} alt=""/>
<div className="col-md-12 rblog_txt text-center mx-auto">
<h6 className="mblog_category mx-auto m-4">{item.category}</h6>
<h2 className="blogs_title col-md-10 mx-auto">{item.title}</h2>
<div className="mt-5 mblog_innertxt col-md-11 mx-auto">{item.text}</div>
<div className="readm mt-5"><i>READ MORE</i></div>
</div>
</div>
</div>
))
}
</div>
</div>
);
}
export default EditorBlog;
this is my second component in which uploaded image url must be send into main component
import React, {useState} from "react";
import {storage} from "../../firebaseConfig";
import firebase from "firebase";
export default function Uploader() {
const [image , setImage]= useState(null);
const [url , setURL]= useState("");
const [imgURL, setImgURL] = useState('');
const [progress , setProgress]= useState(0);
const [error , setError]= useState("");
const handleChange =e =>{
const file = e.target.files[0];
if (file){
const fileType= file["type"];
const validFileType= ["image/gif", "image/png", "image/jpg", "image/jpeg"];
if (validFileType.includes(fileType)){
setError("");
setImage(file);
}else setError("Siz rasm kiritmadingiz");
}else {
setError("Iltimos faylni tanlang");
}
};
const handleUpload = props=>{
if (image){
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 =>{
setError(error);
},
()=>{
storage.ref("images").child(image.name).getDownloadURL().then(url=>{
setURL(url);
console.log(url);
firebase
.firestore()
.collection('images')
.add({
url
})
.then(()=>{
setURL('')
});
setProgress(0);
});
}
)
}else setError("Xatolik Iltimos rasmni yuklang")
};
return(
<div>
<form >
<div className="form-group" >
<input type="file" className="form-control" onChange={handleChange} />
<button type="button" className="btn btn-primary btn-block" onClick={handleUpload}>Upload</button>
</div>
</form >
<div>
{progress > 0 ? <progress style={{marginLeft: "15px"}} value={progress} max="100"/> :""}
</div>
<div style={{height : "100px", marginLeft: "15px", fontWeight: "600"}}>
<p style={{color: "red"}}>{error}</p>
</div>
<img src={url || 'http://via.placeholder.com/400x300'} alt="Uploaded images" height="300" width="650"/>
</div>
)
}
Using a callback, your EditorBlog component can tell the Uploader component what to do when an upload is complete.
In other words, EditorBlog passes a function called "onComplete" as a prop to Uploader. When the upload is completed, Uploader calls this function, with the URL as an argument.
EditorBlog
export default function EditorBlog() {
const [url , setUrl] = useState(null);
// ...
const handleCompletedUpload = (url) => {
setUrl(url);
}
return (
// ...
<div>
<Uploader onComplete={handleCompletedUpload} />
<img src={url || 'http://via.placeholder.com/400x300'} alt="Uploaded images" height="300" width="650" />
</div>
// ...
);
}
Uploader:
export default function Uploader({ onComplete }) {
// ...
uploadTask.on(
"state_changed", (snapshot) => {
// ... progress update
},
(error) => {
// ... error
},
() => {
// Success. Get the URL and call the prop onComplete
uploadTask.snapshot.ref.getDownloadURL().then(url => {
onComplete(url); // Call the callback provided as a prop
});
})
// ...
}
By the way: Notice how you can get the file reference from the uploadTask directly: uploadTask.snapshot.ref.getDownloadURL(). This way you don't have to create a new reference manually, like storage.ref("images").child(image.name).getDownloadURL().

React Apollo: How to access element Trix-Editor within a Mutation component?

Please, I need help accessing a <trix-editor> element in my Mutation component. I'm using React hook useRef() to access this, but getting null. From Chrome Dev Tools (image below) I can see the element has been referred. I have tried some of the solutions given here & here but with no luck. Problem seems to be in the rendering of the Mutation component. I'm fairly new to React, so not sure if I'm getting this right. What am I doing wrong? Thank you in advance for your help.
My code:
const EditProfile = () => {
const trixInput = useRef(null);
const [state, setState] = useState({
firstName: "",
lastName: "",
bio: "",
avatar: "",
gender: "",
country: ""
});
let userID
let { username } = useParams();
let userFromStore = JSON.parse(sessionStorage.getItem('_ev')));
let history = useHistory();
useEffect(() => {
// trixInput.current.addEventListener("trix-change", event => {
console.log(trixInput.current); // <<<< this prints null to the screen
// })
},[]);
if (userFromStore !== username) {
return (
<div className="alert">
<span className="closebtn" onClick={() => history.push("/console")}>×</span>
<strong>Wanning!</strong> Not authorized to access this page.
</div>
);
}
return (
<Query query={GetAuthUser}>
{({ loading, error, data }) => {
if (loading) return "loading...";
if (error) return `Error: ${error}`;
if (data) {
let {userId, firstName, lastName, avatar, bio, gender, country} = data.authUser.profile;
userID = parseInt(userId);
return (
<Mutation mutation={UpdateUserProfile}>
{(editProfile, { loading }) => (
<div className="card bg-light col-md-8 offset-md-2 shadow p-3 mb-5 rounded">
<article className="card-body">
<form onSubmit={ e => {
e.preventDefault();
editProfile({
variables: {
userId: userID,
firstName: state.firstName,
lastName: state.lastName,
bio: state.bio,
avatar: state.avatar,
gender: state.gender,
country: state.country
}
}).then(({ data: { editProfile: { success, errors } } }) => {
success ? alert(success) : alert(errors[0]["message"]);
});
}}
>
<div className="form-row mb-3">
<div className="col">
<input
type="text"
name="firstName"
placeholder="First name"
className="form-control"
defaultValue={firstName}
onChange={e => setState({firstName: e.currentTarget.value})}
/>
</div>
<div className="col">
<input
type="text"
name="lastName"
placeholder="Last name"
className="form-control"
defaultValue={lastName}
onChange={e => setState({lastName: e.currentTarget.value})}
/>
</div>
</div>
<div className="form-group">
<label className="">Bio</label>
<input
type="hidden"
defaultValue={bio}
name="bio"
id="bio-body"
/>
// <<< having trouble accessing this ref element
<trix-editor input="bio-body" ref={trixInput}/>
</div>
<input type="submit" className="btn btn-primary" value="Update Profile" disabled={loading}/>
</form>
</article>
</div>
)}
</Mutation>
);
}
}}
</Query>
)
}
export default EditProfile;
UPDATE:
For anyone interested, problem was solved by extracting Mutation component to a different file. Reason been Mutation component wasn't mounting on render, only the Query component was mounting. First iteration of the solution is shown as an answer.
EditProfile component
import React, { useState } from 'react';
import { Query } from 'react-apollo';
import { useHistory, useParams } from "react-router-dom";
import { GetAuthUser } from './operations.graphql';
import ProfileMutation from './ProfileMutation'
const EditProfile = (props) => {
const [state, setState] = useState({
firstName: "",
lastName: "",
bio: "",
avatar: "",
gender: "",
country: ""
});
let { username } = useParams();
let userFromStore = JSON.parse(sessionStorage.getItem('_ev'));
let history = useHistory();
if (userFromStore !== username) {
return (
<div className="alert">
<span className="closebtn" onClick={() => history.push("/console")}>×</span>
<strong>Wanning!</strong> Not authorized to access this page.
</div>
);
}
return (
<Query query={GetAuthUser}>
{({ loading, error, data }) => {
if (loading) return "loading...";
if (error) return `Error: ${error}`;
return <ProfileMutation state={state} profile={data.authUser.profile} setState={setState}/>
}}
</Query>
)
}
export default EditProfile;
ProfileMutation component
import React, { useRef, useEffect } from 'react';
import { Mutation } from 'react-apollo';
import { UpdateUserProfile } from './operations.graphql';
import "trix/dist/trix.css";
import "./styles.css"
const ProfileMutation = ({ state, profile, setState }) => {
const trixInput = useRef('');
let { userId, firstName, lastName, avatar, bio, gender, country } = profile;
let userID = parseInt(userId);
// console.log(firstName)
useEffect(() => {
trixInput.current.addEventListener('trix-change', (e) => {
setState({bio: trixInput.current.value})
console.log(trixInput.current.value)
})
},[trixInput])
return (
<Mutation mutation={UpdateUserProfile}>
{(editProfile, { loading }) => (
<div className="card bg-light col-md-8 offset-md-2 shadow p-3 mb-5 rounded">
<article className="card-body">
<form onSubmit={ e => {
e.preventDefault();
editProfile({
variables: {
userId: userID,
firstName: state.firstName,
lastName: state.lastName,
bio: state.bio,
avatar: state.avatar,
gender: state.gender,
country: state.country
}
}).then(({ data: { editProfile: { success, errors } } }) => {
success ? alert(success) : alert(errors[0]["message"]);
//TODO: replace alerts with custom message box
});
}}
>
<div className="form-row mb-3">
<div className="col">
<input
type="text"
name="firstName"
placeholder="First name"
className="form-control"
defaultValue={firstName}
onChange={e => setState({firstName: e.currentTarget.value})}
/>
</div>
<div className="col">
<input
type="text"
name="lastName"
placeholder="Last name"
className="form-control"
defaultValue={lastName}
onChange={e => setState({lastName: e.currentTarget.value})}
/>
</div>
</div>
<div className="form-group">
<label className="">Bio</label>
<input
type="hidden"
defaultValue={bio}
name="bio"
id="bio"
/>
<trix-editor input="bio" ref={trixInput} />
</div>
<input type="submit" className="btn btn-primary" value="Update Profile" disabled={loading}/>
</form>
</article>
</div>
)}
</Mutation>
);
}
export default ProfileMutation;
Hope this helps someone! If anyone has a better solution please post it here. Thanks!

Resources