I have a user model which has a phone_number field. In the User schema, phone_number is defined as type string. Yet, when saving the user's details, the phone_number string gets any leading zeros removed and it gets converted to a number.
Can't find the reason why.
I am using next-auth with a mongodb adapter to initially create the user on the DB, so at first there is no phone_number field for the user. Then, I have the following component for editing the user's details:
import React, { useState } from "react";
import { User } from "../../types";
import CustomInput from "../shared/CustomInput";
function EditProfile({ user }: { user: User }) {
const [updatedUser, setUpdatedUser] = useState<User>(user);
async function saveChanges() {
if (!updatedUser) return;
// This logs it to be a string
console.log(
"typeof updatedUser.phone_number",
typeof updatedUser.phone_number
);
try {
let response = await fetch("http://localhost:3000/api/users", {
method: "PATCH",
body: JSON.stringify(updatedUser),
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",
},
});
response = await response.json();
} catch (error) {
console.log("An error occurred while deleting ", error);
}
}
if (!updatedUser) return <></>;
return (
<section className="section-width py-20">
<h1 className="text-7xl font-fancy">Hello {updatedUser.name}</h1>
<div className="grid grid-cols-2 mt-20">
<div className="flex flex-col space-y-5">
<h2 className="font-extrabold text-lg text-gray-400">Personal Info</h2>
<CustomInput
type="text"
label="Full Name"
placeholder="Full Name"
value={updatedUser.name}
onChange={(name) => setUpdatedUser({ ...updatedUser, name })}
/>
<CustomInput
type="text"
label="Phone Number"
placeholder="Phone Number"
value={updatedUser.phone_number || ""}
onChange={(phone_number) => {
var pattern = /^[0-9]+$|^$/;
const isNumber = pattern.test(phone_number);
if (isNumber) setUpdatedUser({ ...updatedUser, phone_number });
}}
/>
<button className="button" onClick={saveChanges}>
Save details
</button>
</div>
</div>
</section>
);
}
export default EditProfile;
Then I have these as my api/users API routes:
import type { NextApiRequest, NextApiResponse } from "next";
import dbConnect from "../../utils/dbconnect";
import User from "../../models/user";
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "GET") {
const data = req.body;
const { email } = data;
await dbConnect();
const user = await User.findOne({ email });
res.status(201).json({ user });
}
if (req.method === "PATCH") {
const data = req.body;
const { _id, name, phone_number } = data;
// This comes of as type of string
console.log("typeof phone_number", typeof phone_number);
// This prints it normally with the zero
console.log("phone_number", phone_number);
await dbConnect();
const user = await User.findByIdAndUpdate(_id, {
name,
phone_number,
});
// Here phone_number is no longer a string and has no leading zero.
console.log("user", user);
res.status(201).json({ user });
}
}
export default handler;
This is the User model:
import { Schema, model, models } from "mongoose";
export const userSchema = new Schema(
{
name: {
type: String,
required: true,
trim: true,
},
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
},
phone_number: {
type: String,
required: false,
},
avatar: {
type: String,
required: false,
},
},
{
timestamps: true,
}
);
const User = models.User || model("User", userSchema);
export default User;
Related
I'm sending a PDF upload as part of a payload to an in-house tool that is separate from my Next.js application through an API route. I'm having trouble getting the FileList to upload correctly. It's part of a form built with react-hook-form, I'm appending the pdf to formData, and it's being correctly converted to a binary that submits to the API route. However, the API hangs and doesn't do anything. This is what my component looks like:
OnboardingDetails.tsx
import { ChangeEvent } from 'react';
import { useRouter } from 'next/router';
import { Button, Input, Spinner } from '#vero/ui';
import { useOnboardingStep } from 'client-data/hooks/use-onboarding-step';
import { inferMutationInpu } from 'client-data/utils/trpc';
import { env } from 'env/client.mjs';
import { OnboardingPageProps } from 'pages/onboarding';
import { Controller, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
type InputType = inferMutationInput<'users.onboarding-details'>;
export const OnboardingDetails = ({
user,
}: {
user: OnboardingPageProps['user'];
}) => {
const router = useRouter();
useOnboardingStep();
const {
handleSubmit,
register,
watch,
control,
formState: { errors },
} = useForm<InputType>({
defaultValues: {
resume: null,
},
});
const onSubmit = async (values: InputType) => {
if (!values.resume) {
toast.error(`Resume is required.`);
return;
}
const formData = new FormData();
formData.append('resume', values.resume);
await fetch(`/api/upload-user`, {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
});
};
return (
<>
<h3 className="m-0 p-0 text-3xl">What are your contact details?</h3>
<form
className="flex w-full flex-col gap-6"
onSubmit={handleSubmit(onSubmit)}
>
<Controller
name="resume"
control={control}
defaultValue={null}
render={({ field }) => (
<Input
showLabel
label="Resume"
type="file"
onChange={(e) => {
field.onChange(e.target.files);
}}
/>
)}
/>
<Button
type="submit"
className="btn-primary"
disabled={isLoading}
icon={isLoading && <Spinner />}
>
Next
</Button>
</form>
</>
);
};
Like I said, when I submit the form I'm able to see the pdf that I upload converted to a binary, but I don't receive a response; this is what my API route looks like:
upload-user.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { authOptions } from './auth/[...nextauth]';
import { getServerSession } from '#vero/lib/auth';
import { prisma } from '#vero/prisma';
import { uploadUser } from '#vero/rater';
import { env } from 'env/server.mjs';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const body = req.body;
const session = await getServerSession({ req, res }, authOptions);
try {
if (req.method !== 'POST') {
res.status(405).json({ error: 'Method Not Allowed' });
return;
}
if (!session) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
const user = await prisma.user.findUnique({
where: {
id: session.user.id,
},
select: {
id: true,
name: true,
email: true,
recruit: {
select: {
github_username: true,
},
},
},
});
if (!user) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
await uploadUser({
apiKey: env.RATER_API_KEY,
baseUrl: env.RATER_API,
userEmail: user.email as string,
userName: user.name as string,
usernameList: user?.recruit?.github_username as string,
userId: user.id,
runAll: true,
resume: body.resume[0],
});
res.status(200).json({ success: true });
} catch (error) {
res.status(500).json({ success: false, error });
}
};
export default handler;
And then, my uploadUser function:
import axios from 'axios';
import FormData from 'form-data';
import fs from 'fs';
import { z } from 'zod';
const UploadUserInput = z.object({
apiKey: z.string(),
baseUrl: z.string().url(),
usernameList: z.string(),
userEmail: z.string(),
userName: z.string(),
userId: z.string(),
resume: z.any().optional(),
runAll: z.boolean(),
});
const GetUsernameFromIdInput = z.object({
accountId: z.string(),
});
export const getUsernameFromId = async (args: { accountId: string }) => {
const { accountId } = GetUsernameFromIdInput.parse(args);
const res = await fetch(`https://api.github.com/user/${accountId}`);
const userData = await res.json();
return userData.login;
};
export const uploadUser = async (args: {
apiKey: string;
baseUrl: string;
usernameList: string;
userEmail: string;
userName: string;
runAll: boolean;
userId: string;
resume?: File;
}) => {
const {
apiKey,
baseUrl,
usernameList,
userEmail,
userName,
runAll,
userId,
resume,
} = UploadUserInput.parse(args);
const data = new FormData();
data.append('username_list', usernameList);
data.append('user_email', userEmail);
data.append('user_name', userName);
data.append('school_user_id', userId);
data.append('resume', resume);
const config = {
method: 'post',
url: `${baseUrl}/api/upload-user/?runAllScrapers=${runAll}`,
headers: {
Authorization: apiKey,
},
data: data,
};
return await axios(config);
};
To clarify, I need to be able to submit the PDF upload from the client to the server, then send the data to our external tool. I'm receiving the binary from the client, and am even able to log it in the req.body, but I can't do anything with it beyond that. It just makes the API route hang up.
I am trying to query a user by their name field, and I am testing it in insomnia. I have my user schema like so:
const userSchema = mongoose.Schema({
id: { type: String },
name: {
type: String,
required: true,
},
companion: {
type: String,
required: false
},
bio: {
type: String,
required: false
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
min: 5
},
userImage: {
type: String,
required: false,
},
});
And I have my route for /search which runs the getUserBySearch function:
router.get('/search', getUserBySearch)
getUserBySearch logic:
export const getUserBySearch = async (req, res) => {
// tried req.query and req.query.search
const { searchQuery } = req.params
try {
// make the search query not case sensitive
const user = new RegExp(searchQuery, `i`)
//find the user's name using the name field
const userFound = await User.find({name: user})
res.json({data: userFound})
} catch (error) {
res.status(404).json({message: error.message})
}
}
Tested in insomnia under the route: http://localhost:3001/user/search?searchQuery=test
I should only be receiving the users whose name field include test; however I get back 200 response with ALL of the users in the DB. how can I only retrieve the users related to my search query?
In your case http://localhost:3001/user/search?searchQuery=test,
you should be accessing the ?searchQuery=test as req.query.searchQuery:
const { searchQuery } = req.query;
const user = new RegExp(searchQuery, `i`);
Since, you were wrongly reading the query value, you had undefined as a value for user constant, hence getting all users in the DB.
exports.searchRecipe = async(req,res) => {
try {
let searchTerm = req.body.searchTerm;
let user = await user.find( { $text: { $search: searchTerm, $diacriticSensitive: true } });
res.render('search', {user} );
} catch (error) {
res.satus(500).send({message: error.message || "Error Occured" });
}
}
and in the search form try this,#
<form method="POST" action="/search">
<input type="search" name="searchTerm" class="form-control" placeholder="Search..." aria-label="Search">
</form>
try like this ,
So I've encountered an interesting issue where I've been able to use my front end to change a boolean value to true, but not back to false. My code:
updateUser.js (backend)
import express from 'express';
import mongoose from 'mongoose';
import User from '../models/userModel.js';
const app = express();
const uUserRouter = express.Router();
uUserRouter.post('/:id', (req, res) => {
User.findById(req.params.id)
.then(user => {
user.username = req.body.username || user.username;
user.email = req.body.email || user.email;
user.password = req.body.password || user.password;
user.avatar = req.body.avatar || user.avatar;
user.isBanned = req.body.isBanned || user.isBanned;
user.rank = req.body.rank || user.rank;
user.save()
.then(() => res.send('User Updated!'))
.catch(err => res.status(400).send("Update Failed: " + err.message))
})
.catch(err => res.status(404).send('This user cannot be found: ' + err.message))
});
export default uUserRouter;
adminPanel.component.js (frontend)
import axios from 'axios';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Button, Container, Table } from 'react-bootstrap';
import { Link } from 'react-router-dom';
const Topic = props => (
<tr>
<td className="tTitle">
{props.topic.title}
</td>
<td className="tDescription">
{props.topic.description}
</td>
<td><Button variant="secondary" onClick={() => { props.deleteTopic(props.topic._id) }}>Delete</Button></td>
</tr>
)
const User = props => (
<tr>
<td className="aPAvatar"><img src={props.user.avatar} height="15%" width="15%" alt="avatar"/></td>
<td>{props.user.username}</td>
<td>{props.user.rank}</td>
<td>{props.user.isBanned ? <Button variant="primary" onClick={() => { props.unBanUser(props.user._id) }}>Unban User</Button> : <Button variant="warning" onClick={() => { props.banUser(props.user._id) }}>Ban User</Button>}</td>
<td><Button variant="secondary" onClick={() => { props.deleteUser(props.user._id) }}>Delete</Button></td>
</tr>
)
class AdminPanel extends Component {
constructor(props) {
super(props);
this.banUser = this.banUser.bind(this);
this.deleteTopic = this.deleteTopic.bind(this);
this.deleteUser = this.deleteUser.bind(this);
this.unBanUser = this.unBanUser.bind(this);
this.state = {
topics: [],
users: []
};
}
componentDidMount() {
function getTopics() {
return axios.get('http://localhost:5000/forum/topics')
}
function getUsers() {
return axios.get('http://localhost:5000/users')
}
Promise.all([getTopics(), getUsers()])
.then((results) => {
const topics = results[0].data;
const users = results[1].data;
this.setState({topics, users}, () => {
console.log(this.state);
});
}).catch((e) => {
console.log('Error: ', e);
});
}
listTopics = () => {
return this.state.topics.map(currenttopic => {
return <Topic topic={currenttopic} deleteTopic={this.deleteTopic} key={currenttopic._id} />
})
}
listUsers = () => {
return this.state.users.map(currentuser => {
return <User user={currentuser} deleteUser={this.deleteUser} banUser={this.banUser} unBanUser={this.unBanUser} key={currentuser._id} />
})
}
banUser(id) {
if (window.confirm('Are you sure you want to ban this user?')) {
axios.post('http://localhost:5000/users/update/'+id, {isBanned: true})
.then(res => {console.log(res.data)})
.catch((e) => {
console.log('Error: ', e)
});
window.alert('User banned successfully');
window.location.reload();
} else {
window.alert('Ban Canceled');
}
}
unBanUser(id) {
axios.post('http://localhost:5000/users/update/'+id, {isBanned: false})
.then(res => {console.log(res.data)});
console.log('test');
window.alert('User Unbanned');
}
deleteTopic(id) {
if (window.confirm('Are you sure you want to delete this topic?')) {
axios.delete('http://localhost:5000/forum/topics/'+id)
.then(res => console.log(res.data));
this.setState({
topics: this.state.topics.filter(el => el._id !== id)
})
window.alert('Topic Deleted');
} else {
window.alert('Deletion Canceled');
}
}
deleteUser(id) {
if (window.confirm('Are you sure you want to delete this user?')) {
axios.delete('http://localhost:5000/users/delete/'+id)
.then(res => console.log(res.data));
this.setState({
users: this.state.users.filter(el => el._id !== id)
})
window.alert('User Deleted')
} else {
window.alert('Deletion Canceled')
}
}
render() {
return (
<Container>
{this.props.isLoggedIn && this.props.rank === 'Admin' ?
<Container>
<div className="userList">
<h3>Users</h3>
<Table>
<thead>
<tr>
<td>Avatar</td>
<td>Username</td>
<td>Rank</td>
<td>Ban Hammer</td>
<td>Delete</td>
</tr>
</thead>
<tbody>
{this.listUsers()}
</tbody>
</Table>
</div>
<div className="topicList">
<h3>Topics</h3>
<Table>
<thead>
<tr>
<td>Title</td>
<td>Discription</td>
<td>Delete</td>
</tr>
</thead>
<tbody>
{this.listTopics()}
</tbody>
</Table>
<Link to="/new-topic">
<Button variant="secondary" className="newTopic">Create a Topic</Button>
</Link>
</div>
</Container>
:
<Container>
<h1>You must be logged in and an Administrator to view this page. <Link to='/'>Return to Home</Link></h1>
</Container>
}
</Container>
)
};
}
const mapSateToProps = state => {
return {
isLoggedIn: state.login.loggedIn,
rank: state.login.user.rank
}
}
export default connect(mapSateToProps)(AdminPanel);
userModel.js
import mongoose from 'mongoose';
import bcrypt from 'bcrypt'
const Schema = mongoose.Schema;
const SALT_WORK_FACTOR = 10;
const userSchema = new Schema({
username: {
type: String,
required: true,
trim: true,
/*validate: {
validator: function(username) {User.doesNotExist({ username })},
message: "Username already exists"
},*/
minlength: 5
},
email: {
type: String,
/*validate: {
validator: function(email) {User.doesNotExist({ email })},
message: "Email already exists"
}*/
},
password: {type: String,
required: true,
index: {unique: true} },
avatar: {
type: String,
default: `${process.env.AWSFILESERICE}/pics/defaultPP.png`
},
isBanned: {
type: Boolean,
default: false
},
rank: {
type: String,
default: "Default"
},
joined: {
type: Date
},
topics: [{type: Schema.Types.ObjectId, ref: 'Topics'}],
threads: [{type: Schema.Types.ObjectId, ref: 'Topics.Threads'}],
posts: [{type: Schema.Types.ObjectId, ref: 'Topics.Threads.Posts'}]
}, { timestamps: true});
userSchema.pre('save', function(next) {
const user = this;
if (!user.isModified('password')) return next();
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err);
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) return next(err);
user.password = hash;
next();
});
});
});
userSchema.statics.doesNotExist = async function (field) {
return await this.where(field).countDocuments() === 0;
}
userSchema.methods.authenticate = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (isMatch === true) {
return cb(null, isMatch);
} else {
return cb();
}
})
};
const User = mongoose.model('User', userSchema);
export default User;
(ignore the commented out validation field, its an uncompleted experimentation :) )
Every other post to the update route (which should be "users/update/#id") works. i.e. username, password, email, even isBanned = true. Changing it back to false is not working. Yes, the unban button does "work" it sends the request to the backend like its supposed to and the backend sends "user updated" back to the frontend but the isBanned value stays the same in the database. I'm hoping I just overlooked something, but I read everything I could to solve the issue along with rereading my code... Thanks in advance for your help!
Solution
This solution should work if you want to use boolean values strictly,
... // Other user schema data
isBanned: String
Then when saving it use value.toString() to convert the boolean to string, so it saves as an string that contains true/false value, then when needed use
var originalValue = false;
var toSave = originalValue.toString();
console.log(toSave + ", It's a "+typeof toSave);
// When needed to check if the user is banned or not
// I'll ignore DB things
var isBanned = JSON.parse(toSave);
console.log(isBanned + ", It's a "+typeof isBanned);
For me is the easiest and the best solution
Hi I am building an Ecommerce Application using MERN Stack with redux. But having issues when i try to send price array arguement in my fetching function. It is giving me the error that XHR GET http://localhost:3000/api/v1/products?keyword=&page=1&price[gt]=undefined&price[lt]=undefined
[HTTP/1.1 400 Bad Request 12ms]. Here is my Products.jsx Component Code {success:false,message:'Resource not found invalid Price'}
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Slider, Typography } from '#material-ui/core';
import { useAlert } from 'react-alert';
import { clearErrors, getProducts } from '../../actions/productActions';
import Loader from '../layout/Loader/Loader';
import ProductCard from '../Home/ProductCard';
import './Products.css';
import Pagination from 'react-js-pagination';
const Products = ({ match }) => {
const dispatch = useDispatch();
const alert = useAlert();
const [currentPage, setCurrentPage] = useState(1);
const [price, setPrice] = useState(0, 25000);
const { loading, products, productsCount, error, resultPerPage} =
useSelector((state) => state.products);
const keyword = match.params.keyword;
const setCurrentPageNo = (e) => {
setCurrentPage(e);
};
const priceHandler = (e, newPrice) => {
setPrice(newPrice);
};
useEffect(() => {
if (error) {
alert.error(error);
dispatch(clearErrors());
}
dispatch(getProducts(keyword, currentPage, price));
}, [dispatch, keyword, currentPage]);
return (
<>
{loading ? (
<Loader />
) : (
<>
<h2 className='productsHeading'>Products</h2>
<div className='products'>
{products &&
products.map((product) => (
<ProductCard key={product._id} product={product} />
))}
</div>
<div className='filterBox'>
<Typography>Price</Typography>
<Slider
value={price}
onChange={priceHandler}
valueLabelDisplay='auto'
aria-labelledby='range-slider'
min={0}
max={25000}
/>
</div>
{resultPerPage < productsCount && (
<div className='paginationBox'>
<Pagination
activePage={currentPage}
itemsCountPerPage={resultPerPage}
totalItemsCount={productsCount}
onChange={setCurrentPageNo}
nextPageText='Next'
prevPageText='Prev'
firstPageText='1st'
lastPageText='Last'
itemClass='page-item'
linkClass='page-link'
activeClass='pageItemActive'
activeLinkClass='pageLinkActive'
/>
</div>
)}
</>
)}
</>
);
};
export default Products;
And here is my action for getting allproducts
import axios from 'axios';
import {
ALL_PRODUCT_REQUEST,
ALL_PRODUCT_SUCCESS,
ALL_PRODUCT_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
CLEAR_ERRORS,
} from '../constants/productConstants';
export const getProducts =
(keyword = '', currentPage = 1, price = [0, 25000]) =>
async (dispatch) => {
try {
dispatch({
type: ALL_PRODUCT_REQUEST,
});
const { data } = await axios.get(
`/api/v1/products?keyword=${keyword}&page=${currentPage}&price[gt]=${price[0]}&price[lt]=${price[1]}`
);
dispatch({
type: ALL_PRODUCT_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: ALL_PRODUCT_FAIL,
payload: error.response.data.message,
});
}
};
Here is the Reducer for Products
export const productReducer = (state = { products: [] }, action) => {
switch (action.type) {
case ALL_PRODUCT_REQUEST:
return {
loading: true,
products: [],
};
case ALL_PRODUCT_SUCCESS:
return {
loading: false,
products: action.payload.products,
productsCount: action.payload.productsCount,
resultPerPage: action.payload.resultPerPage,
};
case ALL_PRODUCT_FAIL:
return {
loading: false,
error: action.payload,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
I am Using Custom Error Handling Class and here is the code for this
class ErrorResponse extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
Error.captureStackTrace(this, this.constructor);
}
}
module.exports = ErrorResponse;
And here is the middleware function
const ErrorResponse = require('../utils/errorResponse');
const errorHandler = (err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.message = err.message || 'Internal Server Error';
// Wrong Mongodb Id error
if (err.name === 'CastError') {
const message = `Resource not found. Invalid: ${err.path}`;
err = new ErrorResponse(message, 400);
}
// Mongoose duplicate key error
if (err.code === 11000) {
const message = `Duplicate ${Object.keys(err.keyValue)} Entered`;
err = new ErrorResponse(message, 400);
}
// Wrong JWT error
if (err.name === 'JsonWebTokenError') {
const message = `Json Web Token is invalid, Try again `;
err = new ErrorResponse(message, 400);
}
// JWT EXPIRE error
if (err.name === 'TokenExpiredError') {
const message = `Json Web Token is Expired, Try again `;
err = new ErrorResponse(message, 400);
}
res.status(err.statusCode).json({
success: false,
message: err.message,
});
};
module.exports = errorHandler;
Here is my ApiFeatures class for filtering, Pagination and Sorting
class ApiFeatures {
constructor(query, queryStr) {
this.query = query;
this.queryStr = queryStr;
}
search() {
const keyword = this.queryStr.keyword
? {
name: {
$regex: this.queryStr.keyword,
$options: 'i',
},
}
: {};
this.query = this.query.find({ ...keyword });
return this;
}
filter() {
const queryCopy = { ...this.queryStr };
//Removing fields
const removFields = ['keyword', 'page', 'limit'];
removFields.forEach((key) => delete queryCopy[key]);
let queryStr = JSON.stringify(queryCopy);
queryStr = queryStr.replace(/\b(gt|gte|lt|lte)\b/g, (key) => `$${key}`);
this.query = this.query.find(JSON.parse(queryStr));
return this;
}
pagination(resultPerPage) {
const currentPage = Number(this.queryStr.page) || 1;
const skip = resultPerPage * (currentPage - 1);
this.query = this.query.limit(resultPerPage).skip(skip);
return this;
}
}
module.exports = ApiFeatures;
Here is my controller function
const getProducts = asyncHandler(async (req, res, next) => {
const resultPerPage = 8;
const productsCount = await Product.countDocuments();
const apiFeatures = new ApiFeatures(Product.find(), req.query)
.search()
.filter()
.pagination(resultPerPage);
const products = await apiFeatures.query;
res.status(200).json({
success: true,
count: products.length,
productsCount,
resultPerPage,
products,
});
});
My Product Model
const mongoose = require('mongoose');
const productSchema = mongoose.Schema({
name: {
type: String,
required: [true, 'Please Enter product Name'],
trim: true,
},
description: {
type: String,
required: [true, 'Please Enter product Description'],
},
price: {
type: Number,
required: [true, 'Please Enter product Price'],
maxLength: [8, 'Price cannot exceed 8 characters'],
},
ratings: {
type: Number,
default: 0,
},
images: [
{
public_id: {
type: String,
required: true,
},
url: {
type: String,
required: true,
},
},
],
category: {
type: String,
required: [true, 'Please Enter Product Category'],
},
Stock: {
type: Number,
required: [true, 'Please Enter product Stock'],
maxLength: [4, 'Stock cannot exceed 4 characters'],
default: 1,
},
numOfReviews: {
type: Number,
default: 0,
},
reviews: [
{
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
name: {
type: String,
required: true,
},
rating: {
type: Number,
required: true,
},
comment: {
type: String,
required: true,
},
},
],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model('Product', productSchema);
I am not figuring it out that what is the issue coz i have tested this api on postman and it is giving correct results. Help me to resolve this issue
By a first look, the url on which you're sending the request is http://localhost:3000/api/v1/products?keyword=&page=1&price[gt]=undefined&price[lt]=undefined.
I feel the problem is generated by sending undefined values.
First, initialize price in your state to an array of 2 elements if you're accessing that way in the action.
const [price, setPrice] = useState([0, 25000]);
Second, make sure you are getting the right value from your filter
const priceHandler = (e, newPrice) => {
console.log(newPrice) // Maybe the value you need isn't being passed in the call
setPrice(newPrice);
};
Also, you can inspect the value of price coming in the action and see if it's really an array.
export const getProducts =
(keyword = '', currentPage = 1, price = [0, 25000]) =>
async (dispatch) => {
console.log(price) // and look at the value here
try {
dispatch({
type: ALL_PRODUCT_REQUEST,
});
const { data } = await axios.get(
`/api/v1/products?keyword=${keyword}&page=${currentPage}&price[gt]=${price[0]}&price[lt]=${price[1]}`
);
// Rest of the function ...
};
I'm struggling to debug a problem and I'd appreciate any help the community might be able to offer. I'm building my first React app and have built a working Login feature, but after every successful login the user is forced to hard refresh his/her browser in order see the app in a "logged in" state. There is no error logged to the browser console, but our DevTools monitor shows the following error:
"TypeError: Cannot read property 'setState' of undefined"
What's funny is that the login authentication first succeeds, and then immediately seems to try again and fails. After clicking "login," the user must hard refresh the web page in order to make it appear that the login has worked.
I'm stumped. Can anyone see anything wrong with my code? Thank you very much in advance for taking a look!
Here's our LoginPage jsx file that contains the actual login web form:
import React from 'react';
import { Button, Form, FormGroup, Label, Input } from 'reactstrap';
export default class LoginPage extends React.Component {
constructor(props) {
super(props);
//bound functions
this.compileFormData = this.compileFormData.bind(this);
this.handleEmailChange = this.handleEmailChange.bind(this);
this.handlePasswordChange = this.handlePasswordChange.bind(this);
//component state
this.state = {
email: '',
password: '',
};
}
//update state as email value changes
handleEmailChange(e) {
this.setState({ email: e.target.value });
}
//update state as password value changes
handlePasswordChange(e) {
this.setState({ password: e.target.value });
}
compileFormData() {
const { loginFunction } = this.props;
const formData = this.state;
loginFunction(formData);
}
render() {
return (
<div className="row justify-content-center">
<div className="col-10 col-sm-7 col-md-5 col-lg-4">
<Form>
<FormGroup>
<Label for="exampleEmail">Email</Label>
<Input
type="email"
name="email"
id="userEmail"
placeholder="test#mccre.com"
value={this.state.email}
onChange={this.handleEmailChange}
/>
</FormGroup>
<FormGroup>
<Label for="examplePassword">Password</Label>
<Input
type="password"
name="password"
id="userPassword"
placeholder="password"
value={this.state.password}
onChange={this.handlePasswordChange}
/>
</FormGroup>
<Button onClick={this.compileFormData}>Log In</Button>
</Form>
</div>
</div>
);
}
}
Here's our login page Container that renders the login page:
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { logUserIn } from '../../actions/authentication';
import LoginPage from './LoginPage';
export class LoginPageContainer extends React.Component {
constructor(props) {
super(props);
//bound functions
this.logUserInFunction = this.logUserInFunction.bind(this);
}
logUserInFunction(userData) {
const { dispatch } = this.props;
dispatch(logUserIn(userData));
}
render() {
const { authentication } = this.props;
if (authentication.isLoggedIn) {
return (
<Redirect to="/" />
);
}
return (
<div>
<LoginPage loginFunction={this.logUserInFunction} />
</div>
);
}
}
function mapStateToProps(state) {
return {
authentication: state.authentication,
};
}
export default connect(mapStateToProps)(LoginPageContainer);
Here's our API endpoint in which we actually make the database query:
const express = require('express');
const mongoose = require('mongoose');
const passport = require('passport');
const User = require('../../models/user.js');
const router = express.Router();
//configure mongoose promises
mongoose.Promise = global.Promise;
//POST to /register
router.post('/register', (req, res) => {
//Create a user object to save, using values from incoming JSON
const newUser = new User({
username: req.body.username,
firstName: req.body.firstName,
lastname: req.body.lastName,
email: req.body.email,
});
//Save, via passport's "register" method, the user
User.register(newUser, req.body.password, (err, user) => {
//If there's a problem, send back a JSON object with the error
if (err) {
return res.send(JSON.stringify({ error: err }));
}
// Otherwise, for now, send back a JSON object with the new user's info
return res.send(JSON.stringify(user));
});
});
//POST to /login
router.post('/login', async (req, res) => {
//look up user by their email
const query = User.findOne({ email: req.body.email });
const foundUser = await query.exec();
//If they exist, they'll have a username, so add that to our body
if (foundUser) {
req.body.username = foundUser.username;
}
passport.authenticate('local') (req, res, () => {
//If logged in, we should have use info to send back
if (req.user) {
return res.send(JSON.stringify(req.user));
}
//Otherwise return an error
return res.send(JSON.stringify({ error: 'There was an error logging in' }));
});
});
//GET to /checksession
router.get('/checksession', (req, res) => {
if (req.user) {
return res.send(JSON.stringify(req.user));
}
return res.send(JSON.stringify({}));
});
//GET to /logout
router.get('/logout', (req, res) => {
req.logout();
return res.send(JSON.stringify(req.user));
});
module.exports = router;
Here's the action file in which we define the logUserIn() function:
import { decrementProgress, incrementProgress } from './progress';
import 'whatwg-fetch';
//Action Creators
export const loginAttempt = () => ({ type: 'AUTHENTICATION_LOGIN_ATTEMPT' });
export const loginFailure = error => ({ type: 'AUTHENTICATION_LOGIN_FAILURE', error });
export const loginSuccess = json => ({ type: 'AUTHENTICATION_LOGIN_SUCCESS', json });
export const logoutFailure = error => ({ type: 'AUTHENTICATION_LOGOUT_FAILURE', error });
export const logoutSuccess = () => ({ type: 'AUTHENTICATION_LOGOUT_SUCCESS' });
export const sessionCheckFailure = () => ({ type: 'AUTHENTICATION_SESSION_CHECK_FAILURE'});
export const sessionCheckSuccess = json => ({ type: 'AUTHENTICATION_SESSION_CHECK_SUCCESS', json });
//Check User Session
export function checkSession() {
return async (dispatch) => {
//contact the API
await fetch(
//where to contact
'/api/authentication/checksession',
//what to send
{
method: 'GET',
credentials: 'same-origin',
},
)
.then((response) => {
if (response.status === 200) {
return response.json();
}
return null;
})
.then((json) => {
if (json.username) {
return dispatch(sessionCheckSuccess(json));
}
return dispatch(sessionCheckFailure());
})
.catch((error) => dispatch(sessionCheckFailure(error)));
};
}
//Log user in
export function logUserIn(userData) {
return async (dispatch) => {
//turn on spinner
dispatch(incrementProgress());
//register that a login attempt is being made
dispatch(loginAttempt());
//contact login API
await fetch(
//where to contact
'http://localhost:3000/api/authentication/login',
//what to send
{
method: 'POST',
body: JSON.stringify(userData),
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
},
).then((response) => {
if (response.status === 200) {
return response.json();
}
return null;
})
.then((json) => {
if (json) {
dispatch(loginSuccess(json));
this.setState({ redirect: true });
} else {
dispatch(loginFailure(new Error('Authentication Failed')));
}
}).catch((error) => {
dispatch(loginFailure(new Error(error)));
});
//turn off spinner
dispatch(decrementProgress());
};
}
//Log user out
export function logUserOut() {
return async (dispatch) => {
//turn on spinner
dispatch(incrementProgress());
//contact the API
await fetch(
//where to contact
'/api/authentication/logout',
//what to send
{
method: 'GET',
credentials: 'same-origin',
},
)
.then((response) => {
if (response.status === 200) {
dispatch(logoutSuccess());
} else {
dispatch(logoutFailure(`Error: ${response.status}`));
}
})
.catch((error) => {
dispatch(logoutFailure(error));
});
//turn off spinner
return dispatch(decrementProgress());;
};
}
Finally, here's the reducer file that is supposed to update the application's state depending on authentication success / failure:
const initialState = {
firstName: '',
id: '',
isLoggedIn: false,
isLoggingIn: false,
lastName: '',
username: '',
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case 'AUTHENTICATION_LOGIN_ATTEMPT': {
const newState = Object.assign({}, state);
newState.isLoggingIn = true;
return newState;
}
case 'AUTHENTICATION_LOGIN_FAILURE':
case 'AUTHENTICATION_SESSION_CHECK_FAILURE':
case 'AUTHENTICATION_LOGOUT_SUCCESS': {
const newState = Object.assign({}, initialState);
return newState;
}
case 'AUTHENTICATION_LOGIN_SUCCESS':
case 'AUTHENTICATION_SESSION_CHECK_SUCCESS': {
const newState = Object.assign({}, state);
newState.firstName = action.json.firstName;
newState.id = action.json._id;
newState.isLoggedIn = true;
newState.isLoggingIn = false;
newState.lastName = action.json.lastName;
newState.username = action.json.username;
return newState;
}
case 'AUTHENTICATION_LOGOUT_FAILURE': {
//todo: hanle error
return state;
}
default: {
return state;
}
}
}
Found the solution: "the this.setState({ redirect: true });" line needed to be removed from the action file.