In Next.js I'm trying to POST data in MongoDB using react-query and useMutation
I've structured my endpoints in api/users/index.js
import connectDB from "../../../db/connectDB";
import {getUsers, postUser, updateUser, deleteUser} from "../../../db/controllers/AddedUserController";
/**
* #param {import('next').NextApiRequest} req
* #param {import('next').NextApiResponse} res
*/
export default async function handler(req, res) {
await connectDB().catch(() => res.status(405).json({error: "Connection Error"}))
const {method} = req
switch(method) {
case "GET":
await getUsers(req, res)
break;
case "POST":
await postUser(req, res)
break;
case "PUT":
await updateUser(req, res)
break;
case "DELETE":
await deleteUser(req, res)
break;
default:
res.setHeader("Allow", ["GET", "POST", "PUT", "DELETE"])
res.status(405).end(`Method ${method} not allowed`)
}
}
So if the method equals to POST it calls this endpoint in db/controllers/AddedUserController.js
/*** POST http://localhost:3000/api/users ***/
export async function postUser(req, res) {
try {
const {formData} = req.body
console.log("API", formData)
if(!formData || formData === undefined) return res.status(404).json({error: "Form data not provided"})
await AddedUser.create(formData, function(err, data){
return res.status(200).json(data)
});
} catch(error) {
res.status(404).json(error)
}
}
Finally I call this endpoint with this helper function
/*** POST new user ***/
export const addUser = async (formData) => {
const Options = {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(formData)
}
try {
const response = await fetch(`${BASE_URL}/api/users`, Options)
const json = await response.json()
return json
} catch(error) {
console.log(error)
return error
}
}
Then in my AddingForm.js component I call the helper function using useMutation
/*** components ***/
import {Button, InputGroup} from "../index";
import {useMutation} from "react-query";
import {addUser} from "../../../utils/helpers/helper";
import {useState} from "react";
const AddingForm = () => {
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const addMutation = useMutation(addUser)
const newUser = async (e, formData) => {
e.preventDefault()
await addMutation.mutate(formData)
}
return (
<>
<form onSubmit={(e) => newUser(e, {name, email})}>
<InputGroup
label="Name"
name="name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<InputGroup
label="Email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Button className="bg-gray-200" type="submit">Aggiungi</Button>
</form>
</>
)
}
export default AddingForm;
But I got a 404 error with the message "Form data not provided" from db/controllers/AddedUserController.js
if(!formData || formData === undefined) return res.status(404).json({error: "Form data not provided"})
Why is this happening??
Related
I would like to update the data of useLoaderData() after submit a form.
In my case :
export const countriesLoader = async () => {
const res = await fetch("https://restcountries.com/v3.1/all/");
if (!res.ok) {
throw Error("Could not reach the data !");
}
return res.json();
};
<Route index element={<CoutriesList />} loader={countriesLoader} />
on CountriesList element :
const countries = useLoaderData();
new data that i would like to update in countries useLoaderData() :
const findCountry = async () => {
const res = await fetch(
`https://restcountries.com/v3.1/name/${searchText}`
);
const data = res.json();
return data;
};
So when I submit, I would like that the data of countriesLoader to become the data from findCountry.
any solution ? thanks
I think you may be looking to use findCountry as a route action when a form is submitted. Loaders run when a route is loaded for the first time. An action can be dispatched later.
Basic Example:
const router = createBrowserRouter([
{
index: true,
element: <CountriesList />,
loader: countriesLoader,
action: findCountry
}
]);
<RouterProvider router={router} />
const countriesLoader = async () => {
const res = await fetch("https://restcountries.com/v3.1/all/");
if (!res.ok) {
throw Error("Could not reach the data !");
}
return res.json();
};
const findCountry = async ({ request }) => {
const formData = await request.formData();
const res = await fetch(
`https://restcountries.com/v3.1/name/${formData.get("country")}`
);
if (!res.ok) {
throw Error("Could not reach the data !");
}
return res.json();
};
import {
Form,
useActionData,
useLoaderData
} from "react-router-dom";
const CountriesList = () => {
const searchResult = useActionData();
const countriesData = useLoaderData();
return (
<>
...
<Form method="post" replace>
<label>
Search <input required type="text" name="country" />
</label>
<button type="submit">Create</button>
</Form>
...
</>
);
};
I am trying to make a section for my website with a news bar that contains snippets of update posts for my site. To do so, I have successfully created the data schema, routing on my backend for post and get requests. I am routing requests on the client server using axios for my XMLHTTPRequests, Redux for my global state store, and cors. Using my NewsBar component, I wrap my NewsPosts component, which is a collection of rendered NewsPost components.
NewsBar component:
function useQuery() {
return new URLSearchParams(useLocation().search);
}
const NewsBar = () => {
const classes = useStyles();
const query = useQuery();
const page = query.get('page') || 1;
const searchQuery = query.get('searchQuery');
const [currentId, setCurrentId] = useState(0);
console.log(`NewsBar: ${useLocation().search} | query=>${query}`);
console.log(`searchquery: ${searchQuery}`);
return (
<>
<Grow in>
<Grid container direction={'row'} className={classes.newsBar} justifyContent='center'>
<Typography variant='h3'>News Bar</Typography>
<Grid item>
<NewsPosts setCurrentId={setCurrentId} />
</Grid>
</Grid>
</Grow>
</>
)
}
export default NewsBar;
NewsPosts component:
const NewsPosts = ({ setCurrentId }) => {
const { newsPosts, isLoading } = useSelector((state) => state.newsPosts);
const classes = useStyles();
newsPosts.map((newsPost) => {
console.log(newsPost);
})
console.log(new Date().toISOString());
console.log(`newsPosts array: ${typeof newsPosts} ${newsPosts}`)
if (!newsPosts.length && !isLoading) return 'No news posts.';
return (
isLoading ? <LinearProgress /> : (
<Grid container alignItems='stretch' spacing={3}>
{newsPosts?.map((newsPost) => (
<Grid key={newsPost._id} item xs={12} sm={12} md={12} lg={12}>
<NewsPost newsPost={newsPost} setCurrentId={setCurrentId}/>
</Grid>
))}
</Grid>
)
);
};
export default NewsPosts;
I added console logging for each of my routing actions and methods, and unfortunately it seems as though I get an empty array of type Object instead of the page of documents I am supposed to get. Within my console, the only logs that output are from NewsPosts.js.
NewsBar: | query=>
NewsBar.js:27 searchquery: null
NewsPosts.js:17 2022-12-01T20:36:08.958Z
NewsPosts.js:18 newsPosts array: object
On top of that, I checked my network requests and none were made. Could someone attempt to tell me why this is?
Axios code as per request:
import { START_LOADING, END_LOADING, FETCH_POST, FETCH_ALL, DELETE, CREATE } from "../constants/actionTypes";
import * as api from '../api/index.js';
//CREATE ACTIONS -> should follow the standard XMLHTTPRequest operation
export const getNewsPost = (id) => async (dispatch) => {
try {
console.log('actions: action getNewsPost was called');
dispatch({ type: START_LOADING })
const { data } = await api.fetchNewsPost(id);
dispatch({type: FETCH_POST, payload: { newsPost: data }});
console.log(`got post ${data}`);
} catch (error) {
console.log(error);
}
};
export const getNewsPosts = (page) => async (dispatch) => {
try {
console.log('actions: action getNewsPosts was called');
dispatch({ type: START_LOADING });
const {data : {data, currentPage, numberOfPages }} = await api.fetchNewsPosts(page);
dispatch({ type: FETCH_ALL, payload: { data, currentPage, numberOfPages }});
dispatch({ type: END_LOADING });
} catch (error) {
console.log(error);
}
};
export const createNewsPost = (newsPost, history) => async (dispatch) => {
try {
console.log('actions: action createNewsPosts was called');
dispatch({ type: START_LOADING });
const { data } = await api.createNewsPost(newsPost);
dispatch({ type: CREATE, payload: data });
history.push(`/newsPosts/${data._id}`);
} catch (error) {
console.log(error);
}
};
export const deleteNewsPost = (id) => async (dispatch) => {
try {
console.log('actions: action deleteNewsPost was called');
await await api.deletePost(id);
dispatch({ type: DELETE, payload: id });
} catch (error) {
console.log(error);
}
};
index.js
import axios from 'axios';
const API = axios.create({ baseURL: 'http://localhost:5000' });
API.interceptors.request.use((req) => {
if (localStorage.getItem('profile')) {
req.headers.Authorization = `Bearer ${JSON.parse(localStorage.getItem('profile')).token}`;
}
return req;
});
export const fetchPost = (id) => API.get(`/posts/${id}`);
export const fetchPosts = (page) => API.get(`/posts?page=${page}`);
export const fetchPostsByCreator = (name) => API.get(`/posts/creator?name=${name}`);
export const fetchPostsBySearch = (searchQuery) => API.get(`/posts/search?searchQuery=${searchQuery.search || 'none'}&tags=${searchQuery.tags}`);
export const createPost = (newPost) => API.post('/posts', newPost);
export const likePost = (id) => API.patch(`/posts/${id}/likePost`);
export const comment = (value, id) => API.post(`/posts/${id}/commentPost`, { value });
export const updatePost = (id, updatedPost) => API.patch(`/posts/${id}`, updatedPost);
export const deletePost = (id) => API.delete(`/posts/${id}`);
export const signIn = (formData) => API.post('/user/signin', formData);
export const signUp = (formData) => API.post('/user/signup', formData);
export const fetchNewsPost = (id) => API.get(`/news/${id}`);
export const fetchNewsPosts = (page) => API.get(`/news?page=${page}`);
export const createNewsPost = (newNewsPost) => API.post('/news', newNewsPost);
export const deleteNewsPost = (id) => API.delete(`/news/${id}`);
What check did I miss that my edit page only works if it has a file selected?
I tried to make some conditionals in the onSubmit function but I haven't found the way yet.
I know it has to do with the image array because when I click edit without selecting a file I get this error:
TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
<script setup>
import { getAuth, onAuthStateChanged } from 'firebase/auth'
import {
getStorage,
uploadBytesResumable,
ref as firebaseRef,
getDownloadURL
} from 'firebase/storage'
import { doc, updateDoc, getDoc, serverTimestamp } from 'firebase/firestore'
import { db } from '../../firebase.config.js'
import { v4 as uuidv4 } from 'uuid'
const themeEditListing = ref([])
const formData = ref({
name: '',
})
const route = useRoute()
// get the theme id from the route
const themeId = route.params.id
const auth = getAuth()
onAuthStateChanged(auth, (user) => {
if (user) {
// set user on formData
formData.value.user = user.uid
} else {
navigateTo('/')
}
})
const fetchEditListing = async () => {
const docRef = doc(db, 'themes', themeId)
// response
const docSnap = await getDoc(docRef)
if (docSnap.exists) {
themeEditListing.value = docSnap.data()
formData.value = themeEditListing.value
}
}
fetchEditListing()
const onSubmit = async () => {
const newImgUrls = await Promise.all(
Array.from(formData.value.images).map((image) => {
return storeImage(image)
})
).catch((err) => {
console.log(err)
})
const imgUrls = newImgUrls ? formData.value.imgUrls.concat(newImgUrls) : formData.value.imgUrls
const formDataCopy = { ...formData.value, imgUrls, timestamp: serverTimestamp() }
delete formDataCopy.images
// update listing
// get doc reference
const docRef = doc(db, 'themes', themeId)
// update doc
await updateDoc(docRef, formDataCopy)
formData.value = { ...formData.value, imgUrls }
}
const onFileChange = (e) => {
const file = e.target.files
formData.value.images = file
}
// store images in firebase storage
const storeImage = async (image) => {
return new Promise((resolve, reject) => {
formData.value.imgUrls.push('')
newImgProgress.value = 1
const storage = getStorage()
const fileName = `${auth.currentUser.uid}-${image.name}-${uuidv4()}`
const storageRef = firebaseRef(storage, 'images/' + fileName)
const uploadTask = uploadBytesResumable(storageRef, image)
uploadTask.on(
'state_changed',
(snapshot) => {
newImgProgress.value = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
console.log('Upload is ' + newImgProgress.value + '% done')
switch (snapshot.state) {
case 'paused':
console.log('O Upload está parado')
break
case 'running':
console.log('Subindo a Imagem')
break
default:
break
}
},
(error) => {
reject(error)
},
() => {
// Handle successful uploads on complete
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
// pop the last element
formData.value.imgUrls.pop()
newImgProgress.value = 0
resolve(downloadURL)
})
}
)
})
}
</script>
<template>
<div>
<form #submit.prevent="onSubmit" class="w-full border shadow-md p-4 rounded-md">
<div>
<input
type="text"
#value="formData.name"
v-model="formData.name"
placeholder="Nome do Tema"
class="input w-full border-x-indigo-300"
/>
</div>
<div>
<input
type="file"
id="images"
#value="formData.images"
#change="onFileChange"
accept=".jpg, .png, .jpeg, .webp"
multiple
class="hidden"
/>
</label>
</div>
<button type="submit"">Edit Theme</button>
</form>
</div>
</template>
I edited the code as much as possible to make it short to read.
I get this error when I try and call a function I have imported within my useEffect() hook in Dashboard.jsx. I am just trying to pull in data from database on the page load pretty much so that when user click button they can send off correct credentials to the api.
I am pulling it in from database for security reasons, so client id is not baked into the code.
I am pretty sure that I am getting this error maybe because the function is not inside a react component? although I am not 100% sure. And if that is the case I am not sure of the best way to restructure my code and get the desired output.
Code below.
mavenlinkCredentials.js
import { doc, getDoc } from "firebase/firestore";
import { useContext } from "react";
import { AppContext } from "../../context/context";
import { db } from "../../firebase";
const GetMavenlinkClientId = async () => {
const {setMavenlinkClientId} = useContext(AppContext)
const mavenlinkRef = doc(db, 'mavenlink', 'application_id');
const mavenlinkDocSnap = await getDoc(mavenlinkRef)
if(mavenlinkDocSnap.exists()){
console.log("mavenlink id: ", mavenlinkDocSnap.data());
console.log(mavenlinkDocSnap.data()['mavenlinkAccessToken'])
setMavenlinkClientId(mavenlinkDocSnap.data()['application_id'])
} else {
console.log("No doc");
}
}
export default GetMavenlinkClientId;
Dashboard.jsx
import React, { useContext, useEffect, useState } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import { useNavigate } from "react-router-dom";
import { query, collection, getDocs, where, setDoc, doc, getDoc } from "firebase/firestore";
import { auth, db, logout } from "../firebase";
import { Button, Container, Grid, Paper } from "#mui/material";
import ListDividers from "../components/ListDividers";
import { AppContext } from "../context/context";
import axios from "axios";
import {SuccessSnackbar, ErrorSnackbar} from '../components/PopupSnackbar';
import GetMavenlinkClientId from "../helpers/firebase/mavenlinkCredentials";
const Dashboard = () => {
const [user, loading, error] = useAuthState(auth);
const [name, setName] = useState("");
const [ accessToken, setAccessToken ] = useState("")
const [errorAlert, setErrorAlert] = useState(false);
const [successAlert, setSuccessAlert] = useState(false);
const [mavenlinkClientId, setMavenlinkClientId] = useState("");
const {isAuthenticated} = useContext(AppContext);
const navigate = useNavigate();
const uid = user.uid
const parsedUrl = new URL(window.location.href)
const userTokenCode = parsedUrl.searchParams.get("code");
const { mavenlinkConnected, setMavenlinkConnected } = useContext(AppContext)
const { maconomyConnected, setMaconomyConnected } = useContext(AppContext)
const { bambooConnected, setBambooConnected } = useContext(AppContext)
const fetchUserName = async () => {
try {
const q = query(collection(db, "users"), where("uid", "==", user?.uid));
const doc = await getDocs(q);
const data = doc.docs[0].data();
setName(data.name);
} catch (err) {
console.error(err);
alert("An error occured while fetching user data");
}
};
//
useEffect(() => {
if (loading) return;
if (!user) return navigate("/");
fetchUserName();
if(userTokenCode !== null){
authorizeMavenlink();
}
if(isAuthenticated){
GetMavenlinkClientId()
}
}, [user, loading]);
///put this into a page load (use effect maybe) so user does not need to press button to connect to apis
const authorizeMavenlink = () => {
console.log(uid);
const userRef = doc(db, 'users', uid);
axios({
//swap out localhost and store in variable like apitool
method: 'post',
url: 'http://localhost:5000/oauth/mavenlink?code='+userTokenCode,
data: {}
})
.then((response) => {
setAccessToken(response.data);
setDoc(userRef, { mavenlinkAccessToken: response.data}, { merge: true });
setMavenlinkConnected(true);
setSuccessAlert(true);
})
.catch((error) => {
console.log(error);
setErrorAlert(true)
});
}
//abstract out client id and pull in from db
const getMavenlinkAuthorization = () => {
window.open('https://app.mavenlink.com/oauth/authorize?client_id='+mavenlinkClientId+'&response_type=code&redirect_uri=http://localhost:3000');
window.close();
}
const authorizeBamboo = () => {
axios({
method: 'get',
url: 'http://localhost:5000/oauth/bamboo',
data: {}
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error);
});
// console.log('bamboo connected')
setBambooConnected(true);
}
const authorizeMaconomy = () => {
console.log("Maconomy connected")
setMaconomyConnected(true);
}
const syncAccount = async() => {
if(!mavenlinkConnected){
await getMavenlinkAuthorization()
}
if (!bambooConnected){
await authorizeBamboo();
}
if (!maconomyConnected){
await authorizeMaconomy();
}
}
const handleAlertClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setSuccessAlert(false) && setErrorAlert(false);
};
console.log(mavenlinkClientId);
return(
<>
<Container>
<div className="dashboard">
<h1>Dashboard</h1>
<Grid container spacing={2}>
<Grid item xs={12}>
<Paper style={{paddingLeft: "120px", paddingRight: "120px"}} elevation={1}>
<div className="dashboard-welcome">
<h2>Welcome {name}</h2>
<h4>{user?.email}</h4>
<hr/>
<h2>Integrations</h2>
<Button onClick={syncAccount}>
Sync Account
</Button>
{/* <Button onClick={getMavenlinkClientId}>
Bamboo Test
</Button> */}
<ListDividers/>
</div>
</Paper>
</Grid>
</Grid>
</div>
{successAlert === true ? <SuccessSnackbar open={successAlert} handleClose={handleAlertClose}/> : <></> }
{errorAlert === true ? <ErrorSnackbar open={errorAlert} handleClose={handleAlertClose}/> : <></> }
</Container>
</>
);
}
export default Dashboard;
the error is because you’re calling const {setMavenlinkClientId} = useContext(AppContext) inside the file mavenlinkCredentials.js which is not a react components.
you could maybe change the function inside mavenlinkCredentials.js to accept a setMavenlinkClientId and pass it from outside like this.
const GetMavenlinkClientId = async (setMavenlinkClientId) => {
const mavenlinkRef = doc(db, 'mavenlink', 'application_id');
const mavenlinkDocSnap = await getDoc(mavenlinkRef)
if(mavenlinkDocSnap.exists()){
console.log("mavenlink id: ", mavenlinkDocSnap.data());
console.log(mavenlinkDocSnap.data()['mavenlinkAccessToken'])
setMavenlinkClientId(mavenlinkDocSnap.data()['application_id'])
} else {
console.log("No doc");
}
}
and then you can call this function in your dashboard.js like so,
const {setMavenlinkClientId} = useContext(AppContext)
if(isAuthenticated){
GetMavenlinkClientId(setMavenlinkClientId)
}
I'm working on a reviews feature and I'm trying to access data from my MongoDB database and display it in my comment in react. However when I load the page its empty and nothing displays. I console logged my reponse.data and found out the array is empty and I'm unsure why.
client end
this is the component where my reviews are displayed.
const MovieInfoComponent = () => {
const [movieInfo, setMovieInfo] = useState();
const [reviewList, setReviewList] = useState([]);
const {id} = useParams()
useEffect( ()=>{
(async ()=>{
const response = await getDetails(id)
setMovieInfo(response)
//console.log(setMovieInfo);
})();
},[])
const updateReview = (newReview) => {
setReviewList(reviewList.concat(newReview))
}
useEffect(() => {
Axios.post('http://localhost:8080/api/review/getReviews', id, { headers: authHeader()})
.then(response => {
if (response.data.success) {
console.log('response.data.reviews',response.data.reviews)
// setReviewList(response.data.reviews)
} else {
alert('Error')
}
})
}, [])
return (
<Container2>
<Reviews refreshFunction={updateReview} reviewList={reviewList} movieId={movieInfo?.id}/>
</Container2>
)
}
reviews.js
import React, {useState, useRef} from 'react';
import Axios from 'axios';
import {Button, Input} from 'antd';
import authService from '../../services/auth.service'
import authHeader from '../../services/auth-header';
import FirstReview from './FirstReview';
const {TextArea} = Input;
const Reviews = (props) => {
const currentUser = authService.getCurrentUser();
const [review, setReview] = useState('');
const handleChange = (e) => {
setReview(e.target.value)
}
const onSubmit = (e) => {
e.preventDefault();
const variables = {
movieId: props.movieId,
content: review,
author: currentUser.id,
reviewId: props.reviewId,
}
Axios.post('http://localhost:8080/api/review/addReview', variables,{ headers: authHeader()})
.then(response=> {
if(response.data.success) {
setReview("")
props.refreshFunction(response.data.result)
} else {
alert('Failed to save review')
}
})
}
return (
<div>
<p>Reviews</p>
{props.reviewList && props.reviewList.map((review, index) => (
(!review.responseTo &&
<React.Fragment key={review._id}>
<FirstReview review={review} movieId={props.movieId} refreshFunction={props.refreshFunctions}/>
</React.Fragment>
)))}
<form style={{display: 'flex'}} onSubmit>
<TextArea
style={{width: '100%', borderRadius: '5px'}}
placeholder = "leave a review"
value={review}
onChange={handleChange}
/>
<Button style = {{width: '20%', height: '52px'}} onClick={onSubmit}></Button>
</form>
</div>
);
};
export default Reviews
backend
reviews routes file
const express = require('express');
const router = express.Router();
const {authJwt} = require("../middlewares");
const Review = require("../models/review.model")
router.use(function(req, res, next) {
res.header(
"Access-Control-Allow-Headers",
"x-access-token, Origin, Content-Type, Accept"
);
next();
});
router.post("/addReview", [authJwt.verifyToken], (req, res) => {
const review = new Review(req.body)
review.save((err, review) => {
if(err) return res.json({success:false, err})
Review.find({'_id': review._id})
.populate('author')
.exec((err, result) => {
if(err) return res.json({success: false, err})
return res.status(200).json({success: true, result})
})
})
})
router.post("/getReviews", [authJwt.verifyToken], (req, res) => {
Review.find({"movieId": req.body.id})
.populate('author')
.exec((err, reviews) => {
if(err) return res.status(400).send(err)
res.status(200).json({success: true, reviews})
})
})
module.exports = router ;