Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

in edit profile, i am getting req.body is undefined #155

Open
sc0rp10n-py opened this issue Dec 5, 2022 · 9 comments
Open

in edit profile, i am getting req.body is undefined #155

sc0rp10n-py opened this issue Dec 5, 2022 · 9 comments

Comments

@sc0rp10n-py
Copy link

i added some values in the profile and want to edit them, so i did like in code here
https://github.com/hoangvvo/nextjs-mongodb-app/blob/v2/pages/api/user/index.js

const {quote} = req.body;

but i get this error

Cannot destructure property 'baseName' of 'req.body' as it is undefined."

even console.log(req.body) gives undefined.

@danielmeeusen
Copy link

I need to see the code to be able to help you

@sc0rp10n-py
Copy link
Author

So i was able to read the body using this buffer function I found in one of the issues on nextjs github issues

But i am having trouble with sending profile picture to backend to be uploaded or updated.
this is the API code

import { ValidateProps } from '@/api-lib/constants';
import { findUserByEmail, updateUserById } from '@/api-lib/db';
import { auths, validateBody } from '@/api-lib/middlewares';
import { getMongoDb } from '@/api-lib/mongodb';
import { ncOpts } from '@/api-lib/nc';
// import { slugUsername } from '@/lib/user';
import { v2 as cloudinary } from 'cloudinary';
import multer from 'multer';
import nc from 'next-connect';
// import { Readable } from 'node:stream';

const upload = multer({ dest: '/tmp' });
const handler = nc(ncOpts);

if (process.env.CLOUDINARY_URL) {
  const {
    hostname: cloud_name,
    username: api_key,
    password: api_secret,
  } = new URL(process.env.CLOUDINARY_URL);

  cloudinary.config({
    cloud_name,
    api_key,
    api_secret,
  });
}

handler.use(...auths);

handler.get(async (req, res) => {
  if (!req.user) return res.json({ user: null });
  return res.json({ user: req.user });
});

async function buffer(readable) {
  const chunks = [];
  for await (const chunk of readable) {
    chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
  }
  return Buffer.concat(chunks);
}

handler.patch(
  upload.single('profilePicture'),
  validateBody({
    type: 'object',
    properties: {
      username: ValidateProps.user.username,
    },
    additionalProperties: true,
  }),
  async (req, res) => {
    if (!req.user) {
      req.status(401).end();
      return;
    }

    const db = await getMongoDb();

    let profilePicture;
    if (req.file) {
      const image = await cloudinary.uploader.upload(req.file.path, {
        width: 512,
        height: 512,
        crop: 'fill',
      });
      profilePicture = image.secure_url;
    }

    const buf = await buffer(req);
    const rawBody = buf.toString('utf-8');
    const body = JSON.parse(rawBody);
    const { baseName, quote, email } = body;

    // let username;

    if (body.email) {
      // username = slugUsername(req.body.username);
      if (
        email !== body.email &&
        (await findUserByEmail(db, email))
      ) {
        res
          .status(403)
          .json({ error: { message: 'The email has already been taken.' } });
        return;
      }
    }

    const user = await updateUserById(db, req.user._id, {
      // ...(username && { username }),
      ...(profilePicture && { profilePicture }),
      ...(baseName && { baseName }),
      ...(quote && { quote }),
      ...(email && { email }),
    });

    res.status(200).json({ user });
  }
);

export const config = {
  api: {
    bodyParser: false,
  },
};

export default handler;

this is the frontend code

const Game = ({ logoutSound, clickSound }) => {
    const [username, setUsername] = useState("");
    const [email, setEmail] = useState("");
    const [verified, setVerified] = useState(false);
    const [oldPassword, setOldPassword] = useState("");
    const [newPassword, setNewPassword] = useState("");
    const [baseName, setBaseName] = useState("");
    const [quote, setQuote] = useState("");
    const [avatar, setAvatar] = useState("");
    const [avatarUrl, setAvatarUrl] = useState(null);
    const [quoteSize, setQuoteSize] = useState("");

    const router = useRouter();

    useEffect(() => {
        const loggedInUser = sessionStorage.getItem("loggedIn") || false;
        if (loggedInUser) {
            const userInfo = JSON.parse(sessionStorage.getItem("user"));
            console.log(userInfo);
            setUsername(userInfo.user.username);
            setEmail(userInfo.user.email);
            setVerified(userInfo.user.emailVerified);
            setBaseName(userInfo.user.baseName);
            setQuote(userInfo.user.quote);
            setAvatarUrl(userInfo.user.profilePicture);
        } else {
            router.push("/login");
        }
    }, []);

    const updateProfile = async () => {
        // e.preventDefault();
        const formData = new FormData();
        formData.append("username", username);
        formData.append("email", email);
        formData.append("baseName", baseName);
        formData.append("quote", quote);
        formData.append("profilePicture", avatar);
        await fetch("/api/user", {
            method: "PATCH",
            headers: {
                "Content-Type": "application/json",
            },
            body: JSON.stringify({
                username,
                email,
                baseName,
                quote,
                avatar,
            }),
        })
            .then(async (res) => {
                if (res.status === 200) {
                    const data = await res.json();
                    sessionStorage.setItem("user", JSON.stringify(data));
                } else {
                    toast.error("Error updating profile");
                }
            })
            .catch((err) => {
                toast.error("Error updating profile");
            });
    };

    const updatePassword = async () => {
        await fetch("/api/user/password", {
            method: "PUT",
            headers: {
                "Content-Type": "application/json",
            },
            body: JSON.stringify({
                oldPassword,
                newPassword,
            }),
        })
            .then(async (res) => {
                if (res.status === 200) {
                    toast.success("Password updated");
                    await onLogout();
                } else {
                    toast.error("Error updating password");
                }
            })
            .catch((err) => {
                toast.error("Error updating password");
            });
    };

    return (
        <>
            <div>
                <label className="absolute top-[-5px] right-[-5px] bg-dark rounded-full p-[5px] cursor-pointer">
                    <CiEdit size={20} />
                    <input
                        type="file"
                        className="hidden"
                        onChange={(e) => {
                            const file = e.target.files[0];
                            const reader = new FileReader();
                            reader.onload = (l) => {
                                setAvatarUrl(l.target.result);
                            };
                            reader.readAsDataURL(file);
                            setAvatar(file);
                        }}
                    />
                </label>
                {avatarUrl ? (
                    <>
                        <Image
                            src={avatarUrl}
                            className="bg-dark w-20 h-20 rounded-lg"
                            alt={username}
                            width={80}
                            height={80}
                        />
                    </>
                ) : (
                    <>
                        <div className="bg_drop bg-dark w-20 h-20 rounded-lg"></div>
                    </>
                )}
            </div>
            <div className="mb-5 w-3/4">
                <input
                    type="text"
                    className="border rounded-lg px-4 py-2 outline-none text-2xl bg-dark font-bold text-center w-full"
                    placeholder="Your Base Name"
                    //baseName's Base
                    value={baseName}
                    onChange={(e) => {
                        setBaseName(e.target.value);
                    }}
                />
            </div>
            <textarea
                className="border w-3/4 outline-none p-4 bg-dark text-xl rounded-lg mb-5"
                placeholder="Enter your quote here..."
                value={quote}
                onChange={(e) => {
                    setQuote(e.target.value);
                }}
            ></textarea>
            <div className="border p-4 w-3/4 rounded-lg mb-5 bg-dark">
                <div className="mb-5">
                    <label className="text-lg">Username</label>
                    <input
                        type="text"
                        className="block border-b outline-none text-xl bg-transparent"
                        required
                        placeholder="Username"
                        value={username}
                        readonly
                    />
                </div>
                <div className="mb-5">
                    <label className="text-lg">Email</label>
                    <input
                        type="email"
                        className="block border-b outline-none text-xl bg-transparent"
                        required
                        placeholder="Email"
                        value={email}
                        onChange={(e) => {
                            setEmail(e.target.value);
                        }}
                    />
                </div>
            </div>
            <button
                draggable="false"
                className="bg_drop bg-black border rounded-lg pt-2 pb-3 px-14 text-2xl font-semibold mb-4 transition-transform hover:scale-95"
                onClick={updateProfile}
            >
                Save
            </button>
            <div className="border p-4 w-3/4 rounded-lg mb-5 bg-dark">
                <div className="mb-5">
                    <label className="text-lg">Old Password</label>
                    <input
                        type="password"
                        className="block border-b outline-none text-xl bg-transparent"
                        required
                        placeholder="Old Password"
                        value={oldPassword}
                        onChange={(e) => {
                            setOldPassword(e.target.value);
                        }}
                    />
                </div>
                <div className="">
                    <label className="text-lg">New Password</label>
                    <input
                        type="password"
                        className="block border-b outline-none text-xl bg-transparent"
                        required
                        placeholder="New Password"
                        value={newPassword}
                        onChange={(e) => {
                            setNewPassword(e.target.value);
                        }}
                    />
                </div>
            </div>
            <button
                draggable="false"
                className="bg_drop bg-black border rounded-lg pt-2 pb-3 px-14 text-xl font-semibold transition-transform hover:scale-95"
                onClick={updatePassword}
            >
                Update Password
            </button>
        </>
    );
};
export default Game;

@danielmeeusen
Copy link

What are you getting on your api end? Why are you not using any ref's like in the example? Also I don't think you need to stringify formData.

@sc0rp10n-py
Copy link
Author

What are you getting on your api end? Why are you not using any ref's like in the example? Also I don't think you need to stringify formData.

I needed useStates instead of useRef
The API sends empty array {} for profilePicture

Does using useRef instead of useState makes much of a difference in this case?

@danielmeeusen
Copy link

It does, refs and state are completely different things. Personally I would try to build your form the way it is laid out in the example before changing it.

@sc0rp10n-py
Copy link
Author

if do this API call

const updateProfile = async () => {
    // e.preventDefault();
    setLoading(true);
    await fetch("/api/user", {
        method: "PATCH",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            username,
            email,
            baseName,
            quote,
            profilePicture,
        }),
    })
        .then(async (res) => {
            if (res.status === 200) {
                const data = await res.json();
                sessionStorage.setItem("user", JSON.stringify(data));
                toast.success("Profile updated");
                setLoading(false);
            } else {
                toast.error("Error updating profile");
                setLoading(false);
            }
        })
        .catch((err) => {
            toast.error("Error updating profile");
            setLoading(false);
        });
};

then this is the response i get from backend server

{"error":{"message":"\"\" must be object"}}

@sc0rp10n-py
Copy link
Author

ok i fixed that by removing this

// validateBody({
  //   type: 'object',
  //   properties: {
  //     username: ValidateProps.user.username,
  //   },
  //   additionalProperties: true,
  // }),

i forgot to remove it earlier.

only issue is that profile doesn't go as expected

ok let me try using refs

@sc0rp10n-py
Copy link
Author

@danielmeeusen hi
so i used ref but still profilepicture is going as empty only

my code

const Game = ({ logoutSound, clickSound }) => {
    const [username, setUsername] = useState("");
    const [email, setEmail] = useState("");
    const [verified, setVerified] = useState(false);
    const [oldPassword, setOldPassword] = useState("");
    const [newPassword, setNewPassword] = useState("");
    const [baseName, setBaseName] = useState("");
    const [quote, setQuote] = useState("");
    const profilePictureRef = useRef();
    const [avatarUrl, setAvatarUrl] = useState(null);
    const [quoteSize, setQuoteSize] = useState("");
    const [loading, setLoading] = useState(false);

    const updateProfile = async () => {
        // e.preventDefault();
        setLoading(true);
        const profilePicture = profilePictureRef.current.files[0];
        await fetch("/api/user", {
            method: "PATCH",
            headers: {
                "Content-Type": "application/json",
            },
            body: JSON.stringify({
                username,
                email,
                profilePicture,
                quote,
                baseName,
            }),
        })
            .then(async (res) => {
                if (res.status === 200) {
                    const data = await res.json();
                    sessionStorage.setItem("user", JSON.stringify(data));
                    toast.success("Profile updated");
                    setLoading(false);
                } else {
                    toast.error("Error updating profile");
                    setLoading(false);
                }
            })
            .catch((err) => {
                toast.error("Error updating profile");
                setLoading(false);
            });
    };

    return (
        <>
            <div className="z-[9999] fixed top-0 right-0 w-full h-screen bg-gray-500/50 flex justify-center items-center">
                <div className="container mx-auto my-5 h-[90%]">
                    <div className="bg-black bg-[url('/images/background.png')] bg-cover bg-center rounded-lg p-10 h-full overflow-auto flex flex-col scrollable scrollable-light">
                        <div className="flex justify-end">
                            <button
                                draggable="false"
                                className="transition-transform hover:scale-95 mr-5"
                                onClick={() => {
                                    setModalProfile(false);
                                }}
                            >
                                <ImCross className="" />
                            </button>
                        </div>
                        <div className="flex flex-row flex-wrap items-center my-auto">
                            <div className="md:basis-1/3 shrink-0 px-4 flex flex-col items-center justify-center border-r-2">
                                <div className="relative mb-5">
                                    <label className="absolute top-[-5px] right-[-5px] bg-dark rounded-full p-[5px] cursor-pointer">
                                        <CiEdit size={20} />
                                        <input
                                            type="file"
                                            accept="image/*"
                                            ref={profilePictureRef}
                                            className="hidden"
                                            onChange={(e) => {
                                                const file =
                                                    e.currentTarget.files[0];
                                                if (!file) return;
                                                const reader = new FileReader();
                                                reader.onload = (l) => {
                                                    setAvatarUrl(
                                                        l.currentTarget.result
                                                    );
                                                };
                                                reader.readAsDataURL(file);
                                            }}
                                        />
                                    </label>
                                    {avatarUrl ? (
                                        <>
                                            <Image
                                                src={avatarUrl}
                                                className="bg-dark w-20 h-20 rounded-lg"
                                                alt={username}
                                                width={80}
                                                height={80}
                                            />
                                        </>
                                    ) : (
                                        <>
                                            <div className="bg_drop bg-dark w-20 h-20 rounded-lg"></div>
                                        </>
                                    )}
                                </div>
                                <div className="mb-5 w-3/4">
                                    <input
                                        type="text"
                                        className="border rounded-lg px-4 py-2 outline-none text-2xl bg-dark font-bold text-center w-full"
                                        placeholder="Your Base Name"
                                        //baseName's Base
                                        value={baseName}
                                        onChange={(e) => {
                                            setBaseName(e.target.value);
                                        }}
                                    />
                                </div>
                                <textarea
                                    className="border w-3/4 outline-none p-4 bg-dark text-xl rounded-lg mb-5"
                                    placeholder="Enter your quote here..."
                                    value={quote}
                                    onChange={(e) => {
                                        setQuote(e.target.value);
                                    }}
                                ></textarea>
                                <div className="border p-4 w-3/4 rounded-lg mb-5 bg-dark">
                                    <div className="mb-5">
                                        <label className="text-lg">
                                            Username
                                        </label>
                                        <input
                                            type="text"
                                            className="block border-b outline-none text-xl bg-transparent"
                                            required
                                            placeholder="Username"
                                            value={username}
                                            readonly
                                        />
                                    </div>
                                    <div className="mb-5">
                                        <label className="text-lg">Email</label>
                                        <input
                                            type="email"
                                            className="block border-b outline-none text-xl bg-transparent"
                                            required
                                            placeholder="Email"
                                            value={email}
                                            onChange={(e) => {
                                                setEmail(e.target.value);
                                            }}
                                        />
                                    </div>
                                </div>
                                <button
                                    draggable="false"
                                    className="bg_drop bg-black border rounded-lg pt-2 pb-3 px-14 text-2xl font-semibold mb-4 transition-transform hover:scale-95"
                                    onClick={updateProfile}
                                >
                                    Save
                                    {/* {loading ? <Loading /> : "Save"} */}
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </>
    );
};

API code

import { ValidateProps } from '@/api-lib/constants';
import { findUserByEmail, updateUserById } from '@/api-lib/db';
import { auths, validateBody } from '@/api-lib/middlewares';
import { getMongoDb } from '@/api-lib/mongodb';
import { ncOpts } from '@/api-lib/nc';
// import { slugUsername } from '@/lib/user';
import { v2 as cloudinary } from 'cloudinary';
import multer from 'multer';
import nc from 'next-connect';
// import { Readable } from 'node:stream';

const upload = multer({ dest: '/tmp' });
const handler = nc(ncOpts);

if (process.env.CLOUDINARY_URL) {
  const {
    hostname: cloud_name,
    username: api_key,
    password: api_secret,
  } = new URL(process.env.CLOUDINARY_URL);

  cloudinary.config({
    cloud_name,
    api_key,
    api_secret,
  });
}

handler.use(...auths);

handler.get(async (req, res) => {
  if (!req.user) return res.json({ user: null });
  return res.json({ user: req.user });
});

async function buffer(readable) {
  const chunks = [];
  for await (const chunk of readable) {
    chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
  }
  return Buffer.concat(chunks);
}

handler.patch(
  upload.single('profilePicture'),
  // validateBody({
  //   type: 'object',
  //   properties: {
  //     username: ValidateProps.user.username,
  //   },
  //   additionalProperties: true,
  // }),
  async (req, res) => {
    if (!req.user) {
      req.status(401).end();
      return;
    }

    const db = await getMongoDb();

    let profilePicture;
    if (req.file) {
      const image = await cloudinary.uploader.upload(req.file.path, {
        width: 512,
        height: 512,
        crop: 'fill',
      });
      profilePicture = image.secure_url;
    }

    const buf = await buffer(req);
    const rawBody = buf.toString('utf-8');
    const body = JSON.parse(rawBody);
    const { baseName, quote, email } = body;

    // let username;

    if (body.email) {
      // username = slugUsername(req.body.username);
      if (
        email !== body.email &&
        (await findUserByEmail(db, email))
      ) {
        res
          .status(403)
          .json({ error: { message: 'The email has already been taken.' } });
        return;
      }
    }

    const user = await updateUserById(db, req.user._id, {
      // ...(username && { username }),
      ...(profilePicture && { profilePicture }),
      ...(baseName && { baseName }),
      ...(quote && { quote }),
      ...(email && { email }),
    });

    res.status(200).json({ user });
  }
);

export const config = {
  api: {
    bodyParser: false,
  },
};

export default handler;

network tab pic
image

@danielmeeusen
Copy link

danielmeeusen commented Dec 8, 2022

I guess you don't need a ref if you want to do it that way but you sill still need to use a formData interface though.

Here is an example:
https://codesandbox.io/s/thyb0?file=/pages/index.js:523-725

  const updateProfile= async (e) => {
    const body = new FormData();

    body.append("file", avatarUrl);
    body.append("username", username);
    // etc...
    const res = await fetch("/api/user", {
      method: "PATCH",
      body
    });
  };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants