React User Profile Image and Custom Fields Issue

Hi everyone!

I’ve created a profile page for the user, here we have two problems:

  1. 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": {}
}
  1. 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;

you may need to write it like this:

            const avatar = new Moralis.File("avatar1.jpg", data.avatar[0]);
            x = await avatar.save()
            user.set('avatar', x);

@cryptokid thank you very much, I was following the Rarible Clone Videos, and I must have missed that line of code.

Once again, thank you very much.