Hi everyone!
I’ve created a profile page for the user, here we have two problems:
- Uploading files, Avatar and Banner, In both cases it saves the data but no URL is returned.
{
"_name": "avatar1.jpg",
"_source": {
"format": "file",
"file": {
"name": "facebook_1636116672237_6862370702831370863.jpg",
"lastModified": 1636116757875,
"lastModifiedDate": {
"__type": "Date",
"iso": "2021-11-05T12:52:37.875Z"
},
"webkitRelativePath": "",
"size": 37023,
"type": "image/jpeg"
},
"type": ""
},
"_metadata": {},
"_tags": {}
}
- Saving custom fields like bio and social networks, it saves but sometimes it throws error (see below) in the console and saves those fields as empty.
Your help is greatly appreciated.
Console error that causes to save the custom fields as empty, this Invalid attribute name shows multiple times, from 0 to something like 12.
Warning: Invalid attribute name: `0`
at button
at div
at div
at form
at div
at div
at section
at Account (http://localhost:3000/static/js/main.chunk.js:302:71)
at div
at Activity (http://localhost:3000/static/js/main.chunk.js:15212:1)
at Route (http://localhost:3000/static/js/vendors~main.chunk.js:236243:29)
at Switch (http://localhost:3000/static/js/vendors~main.chunk.js:236445:29)
at Router (http://localhost:3000/static/js/vendors~main.chunk.js:235878:30)
at BrowserRouter (http://localhost:3000/static/js/vendors~main.chunk.js:235498:35)
at div
at MyRouts (http://localhost:3000/static/js/main.chunk.js:14890:33)
at div
at App (http://localhost:3000/static/js/main.chunk.js:57:5)
at MoralisDappProvider (http://localhost:3000/static/js/main.chunk.js:14479:5)
at MoralisProvider (http://localhost:3000/static/js/vendors~main.chunk.js:234087:21)
at Application
The code
import React, { useEffect, useState } from 'react';
import { useForm, useWatch } from "react-hook-form";
import { useMoralis } from "react-moralis";
import { avatarPlaceholder, bytesToSize, maxAvatarSize, maxBannerSize } from '../../helpers/fileUpload';
import { emailPattern, urlPattern, validateFileSize, validateFileType } from '../../helpers/validation';
import ReactTooltip from 'react-tooltip';
import Moralis from 'moralis/node';
function AvatarWatched({ control }) {
const avatar = useWatch({
control,
name: "avatar",
defaultValue: avatarPlaceholder
});
let img = avatarPlaceholder;
if(avatar instanceof FileList && avatar.length > 0) {
img = URL.createObjectURL(avatar[0]);
}
return <img src={img} alt="Profile Avatar" />;
}
function BannerWatched({ control }) {
const banner = useWatch({
control,
name: "banner",
defaultValue: avatarPlaceholder
});
if(banner instanceof FileList && banner.length > 0) {
const img = URL.createObjectURL(banner[0]);
return <img src={img} alt="Profile Banner" style={{ objectFit: 'contain' }} />;
}
return '';
}
function Account() {
const { user } = useMoralis();
const { register, handleSubmit, control, formState: { errors } } = useForm();
const [formSaving, setFormSaving] = useState(false);
const [formSuccess, setFormSuccess] = useState(false);
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [bio, setBio] = useState("");
const [twitterhandler, setTwitterHandler] = useState("");
const [instagramhandler, setInstagramHandler] = useState("");
const [facebookurl, setFacebookURL] = useState("");
const [websiteurl, setWebsiteURL] = useState("");
const [defaultAvatar, setDefaultAvatar] = useState(avatarPlaceholder);
const [avatarPreview, setAvatarPreview] = useState(avatarPlaceholder);
useEffect(()=> {
if (user) {
setUsername(user.attributes.username)
const userEmail = user.get('email');
if(userEmail) {
setEmail(userEmail)
}
const userAvatar = user?.attributes?.avatar?._url;
if(userAvatar) {
setAvatarPreview(userAvatar);
setDefaultAvatar(userAvatar);
}
/* const userBanner = user?.attributes?.banner?._url;
if(userBanner) {
setAvatarPreview(userBanner.url());
setDefaultAvatar(userBanner.url());
} */
const userBio = user.get('bio');
if(userBio) {
setBio(userBio)
}
const userTwitterHandler = user.get('twitterhandler');
if(userTwitterHandler) {
setTwitterHandler(userTwitterHandler)
}
const userInstagramHandler = user.get('instagramhandler');
if(userInstagramHandler) {
setInstagramHandler(userInstagramHandler)
}
const userFacebookURL = user.get('facebookurl');
if(userFacebookURL) {
setFacebookURL(userFacebookURL)
}
const userWebsiteURL = user.get('websiteurl');
if(userWebsiteURL) {
setWebsiteURL(userWebsiteURL)
}
}
}, [user])
const onSubmit = async data => {
setFormSuccess(false);
setFormSaving(true);
user.set('username', data.username);
user.set('email', data.email);
if(data.avatar.length > 0) {
const avatar = new Moralis.File("avatar1.jpg", data.avatar[0]);
user.set('avatar', avatar);
}
if(data.banner.length > 0) {
const banner = new Moralis.File("banner1.jpg", data.banner[0]);
user.set('banner', banner);
}
user.set('bio', data.bio);
user.set('twitterhandler', data.twitterhandler);
user.set('instagramhandler', data.instagramhandler);
user.set('facebookurl', data.facebookurl);
user.set('websiteurl', data.websiteurl);
await user.save();
setFormSuccess(true);
setFormSaving(false);
}
return (
<section className="author-area profile-area">
<div className="row">
<div className="col-12 col-md-6 offset-md-3">
{formSuccess ? (
<div className="alert alert-success" role="alert">Profile updated!</div>
) : ''}
<form className="item-form card no-hover row" onSubmit={handleSubmit(onSubmit)}>
<div className="row">
<div className="col-12">
<div className="form-group mt-3">
<label className="form-label" htmlFor="username">Username</label>
<input type="text" className={errors.name ? "form-control is-invalid" : "form-control"} name="username" id="username" placeholder="Username" maxLength={256} {...register("username", { required: true })} defaultValue={username} />
{errors.name ? (
<div className="invalid-feedback">Required Field.</div>
) : ('')}
</div>
</div>
<div className="col-12">
<div className="form-group mt-3">
<label className="form-label" htmlFor="email">Email</label>
<input type="email" className={errors.email ? "form-control is-invalid" : "form-control"} maxLength={256} name="email" id="email" placeholder="Email" {...register("email", { required: true, pattern: emailPattern })} defaultValue={email} />
{errors.email ? (
<div className="invalid-feedback">
{ errors.email?.type === "required" && <div>Required Field.</div> }
{ errors.email?.type !== "required" && <div>Please enter a valid e-mail.</div> }
</div>
) : ('')}
</div>
</div>
<div className="col-12">
<div className="form-group mt-3">
<label className="form-label" htmlFor="bio">Bio</label>
<textarea className="form-control" maxLength={4000} name="bio" id="bio" rows={7} placeholder="Tell something about you" {...register("bio")} defaultValue={bio} />
</div>
</div>
<div className="col-12 col-md-6">
<div className="form-group">
<label className="form-label mw-310 mhauto">Avatar <span data-tip data-for="bannerTooltip"><i className="fas fa-info"></i></span>
<ReactTooltip id="bannerTooltip">
<div>Recomended 1400x400px</div>
<div>Max size: {bytesToSize(maxBannerSize)}</div>
</ReactTooltip>
</label>
<div className="imgUploadPreviewWrapper">
<div className="imgUploadPreview">
<AvatarWatched control={control} />
</div>
<div className="imgUploadPreviewInput">
<input type="file" name="avatar" id="avatarFile" accept=".jpg, .jpeg, .png" {...register("avatar", {
validate: {
fileSize: files => validateFileSize(files[0]?.size, maxAvatarSize) || 'Max ' + bytesToSize(maxAvatarSize),
acceptedFormats: files =>
validateFileType(files[0]?.type, ['image/jpeg', 'image/png', 'image/gif']) || 'Only PNG, JPEG e GIF',
}
})} style={{ display: "none" }} />
<label htmlFor="avatarFile" className="imgUploadPreviewOverlay">
<div className="imgUploadPreviewOverlayInner">
<i className="fas fa-pencil-alt"></i>
</div>
</label>
</div>
</div>
{errors.avatar ? (
<div className="invalid-feedback mw-310 mhauto">
{ errors.avatar?.type === "fileSize" && (
<div>Max {bytesToSize(maxAvatarSize)}</div>
)}
{ errors.avatar?.type === "acceptedFormats" && <div>Only PNG, JPEG e GIF.</div> }
</div>
) : ('')}
</div>
</div>
<div className="col-12 col-md-6">
<div className="form-group">
<label className="form-label mw-310 mhauto">Banner <span data-tip data-for="bannerTooltip"><i className="fas fa-info"></i></span>
<ReactTooltip id="bannerTooltip">
<div>Recomended 1400x400px</div>
<div>Max size: {bytesToSize(maxBannerSize)}</div>
</ReactTooltip>
</label>
<div className="imgUploadPreviewWrapper">
<div className="imgUploadPreview">
<BannerWatched control={control} />
</div>
<div className="imgUploadPreviewInput">
<input type="file" name="banner" id="bannerFile" accept=".jpg, .jpeg, .png" {...register("banner", {
validate: {
fileSize: files => validateFileSize(files[0]?.size, maxBannerSize) || 'Max ' + bytesToSize(maxBannerSize),
acceptedFormats: files =>
validateFileType(files[0]?.type, ['image/jpeg', 'image/png', 'image/gif']) || 'Only PNG, JPEG e GIF',
}
})} style={{ display: "none" }} />
<label htmlFor="bannerFile" className="imgUploadPreviewOverlay">
<div className="imgUploadPreviewOverlayInner">
<i className="fas fa-pencil-alt"></i>
</div>
</label>
</div>
</div>
{errors.banner ? (
<div className="invalid-feedback mw-310 mhauto">
{ errors.banner?.type === "fileSize" && (
<div>Max {bytesToSize(maxBannerSize)}</div>
)}
{ errors.banner?.type === "acceptedFormats" && <div>Only PNG, JPEG e GIF.</div> }
</div>
) : ('')}
</div>
</div>
<div className="col-12 mt-3 profile-social-links">
<label className="form-label" style={{ paddingLeft: '15px', paddingRight: '15px' }}>Links</label>
<div className="row">
<div className="col-12 col-md-6">
<div className="form-group">
<div className="input-group flex-nowrap">
<div className="input-group-prepend">
<span className="input-group-text" id="addon-wrapping">
<i className="fab fa-twitter"></i>
</span>
</div>
<input type="text" name="twitterhandler" className="form-control" placeholder="Your Twitter Handle" aria-label="twitterhandler" {...register("twitterhandler")} defaultValue={twitterhandler} />
</div>
</div>
</div>
<div className="col-12 col-md-6">
<div className="form-group">
<div className="input-group flex-nowrap">
<div className="input-group-prepend">
<span className="input-group-text" id="addon-wrapping">
<i className="fab fa-instagram"></i>
</span>
</div>
<input type="text" name="instagramhandler" className="form-control" placeholder="Your Instagram Handle" aria-label="instagramhandler" {...register("instagramhandler")} defaultValue={instagramhandler} />
</div>
</div>
</div>
<div className="col-12 col-md-6">
<div className="form-group">
<div className="input-group flex-nowrap">
<div className="input-group-prepend">
<span className="input-group-text" id="addon-wrapping">
<i className="fab fa-facebook-f"></i>
</span>
</div>
<input type="text" name="facebookurl" className={errors.facebookurl ? "form-control is-invalid" : "form-control"} placeholder="Your Facebook" aria-label="facebookurl" {...register("facebookurl", { pattern: urlPattern })} defaultValue={facebookurl} />
</div>
</div>
{errors.facebookurl && <div className="invalid-feedback" style={{ display: 'block' }}>Please enter a valid URL.</div> }
</div>
<div className="col-12 col-md-6">
<div className="form-group">
<div className="input-group flex-nowrap">
<div className="input-group-prepend">
<span className="input-group-text" id="addon-wrapping">
<i className="fas fa-tablet-alt"></i>
</span>
</div>
<input type="text" name="websiteurl" className={errors.websiteurl ? "form-control is-invalid" : "form-control"} placeholder="Your Website" aria-label="websiteurl" {...register("websiteurl", { pattern: urlPattern })} defaultValue={websiteurl} />
</div>
{errors.websiteurl && <div className="invalid-feedback" style={{ display: 'block' }}>Please enter a valid URL.</div> }
</div>
</div>
</div>
</div>
<div className="col-12 col-md-4 offset-md-8">
<button className="btn w-100 mt-3 mt-sm-4" type="submit"{...formSaving ? ' disabled' : ''}>{formSaving ? 'Saving' : 'Save' }</button>
</div>
</div>
</form>
</div>
</div>
</section>
);
}
export default Account;