Why express server receives front end data as undefined? - reactjs

I am currently working on social media mern stack react app. I am using node js and express as my backend services , also using mongoose to store my data and axios and redux thunk which connect the backend to the front end. Till now I had no issue recieving and sending data to the server. Right now I am trying to create search post get request ,base on a keyword the user entered. The issue with it, that when I am sending the keyword to the server instead of recieving the string it gets undefined value, like redux thunk not sending anything. I will be very thankful if someone could help me with that. I am watching the code over and over again and can't find out the reason for that.
My post controller class(I copied only the relevant function):
import express from "express";
const app = express();
import Post from "../model/PostModel.js";
import ErrorHandlng from "../utilities/ErrorHandling.js";
import bodyParser from "body-parser";
import catchAsync from "../utilities/CatchAsync.js";
import User from "../model/UserModel.js";
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
export const getPosts = catchAsync(async (req, res, next) => {
const data = req.body.keyword;
const page = parseInt(req.query.page || "0");
const PAGE_SIZE = 20;
const query = new RegExp(data, "i");
const total = await Post.countDocuments({});
const posts = await Post.find({ $or: [{ title: query }, { content: query }] })
.limit(PAGE_SIZE)
.skip(PAGE_SIZE * page);
if (!posts) {
return next(new ErrorHandlng("No posts were found", 400));
}
res.status(200).json({
status: "success",
data: {
totalPages: Math.ceil(total / PAGE_SIZE),
posts,
},
});
});
My api class(front end,copied only the calling for that specific get request):
import axios from "axios";
const baseURL = "http://localhost:8000";
axios.defaults.withCredentials = true;
const API = axios.create({
baseURL,
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
export const getPostsByKeyword = (keyword, page) =>
API.get(`/post/getPostsByKey?page=${page}`, keyword);
Post slice class:
export const fetchPostsByKeyWord = createAsyncThunk(
"post/getKeyword",
async ({ keyword, page }, { fulfillWithValue, rejectWithValue }) => {
try {
const response = await api.getPostsByKeyword(keyword, page);
if (response.statusCode === "400") {
throw new Error("There are no available posts");
}
const fetchData = await response.data.data.posts;
const totalPages = await response.data.data.totalPages;
return fulfillWithValue({ fetchData, totalPages });
} catch (err) {
console.log(err.response.message);
}
}
);
const initialState = { status: "undefined" };
const PostSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: {},
});
export const postActions = PostSlice.actions;
export default PostSlice;
Calling the backend:
dispatch(fetchPostsByKeyWord({ keyword, page }))
.unwrap()
.then((originalPromiseResults) => {
console.log("thte " + " " + originalPromiseResults.totalPages);
console.log("The data is" + originalPromiseResults.fetchData);
setTotalPages(originalPromiseResults.totalPages);
})
.catch((err) => {
console.log(err.message);
});
As you can see I have not copied the whole code, I copied only the parts that are relevants for the question.

Browsers cannot currently send GET requests with a request body. XMLHttpRequest (which Axios uses) will ignore it and fetch() will trigger an error.
See also HTTP GET with request body for extra discussion on why trying this might be a bad idea.
You should instead pass everything required in the query string, preferably via the params option so it is correctly encoded...
export const getPostsByKeyword = (keyword, page) =>
API.get("/post/getPostsByKey", { params: { page, keyword } });
and grab the data via req.query server-side.
const { page, keyword } = req.query;
With vanilla JS, you can use URLSearchParams to construct the query string...
const params = new URLSearchParams({ page, keyword });
// XHR
const xhr = new XMLHttpRequest();
xhr.open("GET", `/post/getPostsByKey?${params}`);
// Fetch
fetch(`/post/getPostsByKey?${params}`); // GET is the default method
Your Axios instance creation could also be a lot simpler...
Axios is usually quite good at setting the correct content-type header, you don't have to
Your Express app isn't doing any content-negotiation so you don't need to set the accept header
Unless you're actually using cookies (which it doesn't look like), you don't need credential support
const API = axios.create({ baseURL });

Related

sessionStorage not available immediately after navigate

I am trying to implement an React solution with Strapi as backend where authorization is done using JWT-keys. My login form is implemented using the function below:
const handleLogin = async (e) => {
let responsekey = null
e.preventDefault();
const data = {
identifier: LoginState.username,
password: LoginState.password
}
await http.post(`auth/local`, data).then((response) => {
setAuth({
userid: response.data.user.id,
loggedin: true
})
responsekey = response.data.jwt
setLoginState({...LoginState, success: true});
sessionStorage.setItem('product-authkey', responsekey);
navigate('/profile');
}).catch(function(error) {
let result = ErrorHandlerAPI(error);
setLoginState({...LoginState, errormessage: result, erroroccurred: true});
});
}
The API-handler should return an Axios item which can be used to query the API. That function is also shown below. If no API-key is present it should return an Axios object without one as for some functionality in the site no JWT-key is necessary.
const GetAPI = () => {
let result = null
console.log(sessionStorage.getItem("product-authkey"))
if (sessionStorage.getItem("product-authkey") === null) {
result = axios.create(
{
baseURL: localurl,
headers: {
'Content-type': 'application/json'
}
}
)
} else {
result = axios.create({
baseURL: localurl,
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${sessionStorage.getItem("product-authkey")}`
}
})
}
return result
}
export default GetAPI()
However, once the user is redirected to the profile page (on which an API-call is made which needs an JWT-key), the request fails as there is no key present in the sessionStorage. The console.log also shows 'null'. If I look at the DevTools I do see that the key is there... And if I refresh the profile page the request goes through with the key, so the key and backend are working as they should.
I tried making the GetAPI function to be synchronous and to move the navigate command out of the await part in the handleLogin function, but that didn't help.
Does someone have an idea?
Thanks!
Sincerely,
Jelle
UPDATE:
Seems to work now, but I need to introduce the getAPI in the useEffect hook, I am not sure if that is a good pattern. This is the code of the profile page:
useEffect(() => {
let testapi = GetAPI()
const getMatches = async () => {
const response = await testapi.get(`/profile/${auth.userid}`)
const rawdata = response.data.data
... etc
}, [setMatchState]
export default GetAPI() this is the problematic line. You are running the GetApi function when the module loads. Basically you only get the token when you visit the site and the js files are loaded. Then you keep working with null. When you reload the page it can load the token from the session storage.
The solution is to export the function and call it when you need to make an api call.

FormData with NextJS API

Background
I am trying to create a simple CRUD application using NextJS along with react-redux, so what it does is that it saves peoples contacts.So when adding a contact i am trying to send some data along with a file to a NextJS API.
Issue
ContactAction.js
Make a POST request from redux action to add a contact
export const addContact = (data) => async (dispatch) => {
try {
var formData=new FormData();
formData.append('name',data.Name);
formData.append('email',data.Email);
formData.append('phone',data.Phone);
formData.append('image',data.Image);
let response= await Axios.post(`http://localhost:3000/api/contact/addContact`,formData,{
headers:{
'x-auth-token':localStorage.getItem('token')
}
});
} catch (error) {
console.log(error);
}
}
addContact.js
This is the API route in /api/contact/
const handler = async (req, res) => {
switch(req.method){
case "POST":{
await addContact(req,res)
}
}
}
const addContact = async (req, res) => {
console.log(req.body);
// do some stuff here and send response
}
this is what i get in the terminal after the log,also the file is Gibberish as well when logging req.files
Current Effort
I tried using third party packages such as formidable and formidable-serverless but got no luck. so after a day i made it work with a package called multiparty.
addContact.js
const handler = async (req, res) => {
switch(req.method){
case "POST":{
let form = new multiparty.Form();
let FormResp= await new Promise((resolve,reject)=>{
form.parse(req,(err,fields,files)=>{
if(err) reject(err)
resolve({fields,files})
});
});
const {fields,files} = FormResp;
req.body=fields;
req.files=files;
await addContact(req,res)
}
}
}
const addContact = async (req, res) => {
console.log(req.body); //Now i get an Object which i can use
// do some stuff here and send response
}
The above solution is obviously redundant and probably not the best way to go about it plus i don't want to add these 7 8 lines into each route.
so if someone could help me understand what i am doing wrong and why formData doesn't seem to work with NextJS API (when it works with the Express server) i would be grateful.
FormData uses multipart/form-data format. That is not a simple POST request with a body. It is generally used for uploading files, that's why it needs special handling. As an alternative, you could use JSON.
Here is my solution, i hope this helps anybody.
First of all you need to install next-connect and multer as your dependencies.
Now you can use this API route code.
import nextConnect from "next-connect";
import multer from "multer";
const apiRoute = nextConnect({
onError(error, req, res) {
res.status(501).json({ error: `Sorry something Happened! ${error.message}` });
},
onNoMatch(req, res) {
res.status(405).json({ error: `Method "${req.method}" Not Allowed` });
},
});
apiRoute.use(multer().any());
apiRoute.post((req, res) => {
console.log(req.files); // Your files here
console.log(req.body); // Your form data here
// Any logic with your data here
res.status(200).json({ data: "success" });
});
export default apiRoute;
export const config = {
api: {
bodyParser: false, // Disallow body parsing, consume as stream
},
};
Here is an example about uploading file with Next.js:
https://codesandbox.io/s/thyb0?file=/pages/api/file.js
The most important code is in pages/api/file.js
import formidable from "formidable";
import fs from "fs";
export const config = {
api: {
bodyParser: false
}
};
const post = async (req, res) => {
const form = new formidable.IncomingForm();
form.parse(req, async function (err, fields, files) {
await saveFile(files.file);
return res.status(201).send("");
});
};
const saveFile = async (file) => {
const data = fs.readFileSync(file.path);
fs.writeFileSync(`./public/${file.name}`, data);
await fs.unlinkSync(file.path);
return;
};
Generally speaking,in your api file,you should disable the default bodyParser,and write your own parser

React PWA - How do you detect if the requested data to be served from cache or from server

I am building a PWA app, where I download data by a user action call, "Go to offline". So once the button is clicked the data is fetched and saved to the browser indexdb storage. I am using workbox and CRA2. I folled this blog post to customize workbox config with cra app.
Now say there is no PWA, and when page requests data, react call actions for example:
export async function fetchProjectStatuses(
dispatch,
params = {},
raiseError = false
) {
try {
dispatch(requestProjectStatuses());
const data = await fetchProjectStatusesApi();
dispatch(receiveProjectStatuses(data));
return data;
} catch (err) {
if (raiseError) {
throw err;
} else {
dispatch(requestProjectStatusesFailed(err));
}
}
}
and fetchProjectStatusesApi is defined as:
import axios from "axios";
const fetchAllUrl = () => `/project_statuses.json`;
export async function fetchProjectStatusesApi(config, params = {}) {
const url = fetchAllUrl();
const { data } = await axios.get(url, {
headers: { Accept: "application/json" }
});
return data;
}
This works. Now when offline, I am trying to write something like:
import { registerRoute } from "workbox-routing";
registerRoute(
new RegExp("/project_statuses\\.json"),
async ({ url, event, params }) => {
try {
const response = await fetch(event.request);
const responseBody = await response.text();
return new Response(responseBody);
} catch (err) {
// get from DB
}
}
);
So how do I write handler so that it forwards the data to the fetchProjectStatusesApi if network is present , else get data from DB. I know how to pull the date from local IDB. Only thing I am not able to figure out:
How do I detect the app is offline so the data has to come from local db
If app is online how do I forward the response from fetch to axios which is called from the api function.
I am writing it first time so I have no idea yet. Any help will be appreciated.
Thank you.

req.body.something returns undefined

I have been trying to post data to my express server using axios, and when I console.log(req.body.something) it returns undefined, and when I console.log(req.body) only it logs this message to the console:
[Object: null prototype] { '{"nameVal":"Usef","nickNameVal":"US"}': '' }
Any Help Will Be Appreciated.
// This My Server.js Code
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
app.use(bodyParser.json());
// create application/x-www-form-urlencoded parser
const urlencodedparser = bodyParser.urlencoded({ extended: false });
// Use Cors As MiddleWhere
app.use(cors());
// Get The Post Request
app.post("/user", urlencodedparser, (req, res) => {
console.log(req.body.name); // returns undefined
});
app.listen(5000);
// and this the react component state along with the axios post request
state = {
nameVal: "Usef",
nickNameVal: "US"
};
handleSubmit = event => {
event.preventDefault();
const { nameVal, nickNameVal } = this.state;
axios.post("http://localhost:5000/user", { nameVal, nickNameVal },
{ headers: { "Content-Type": "application/x-www-form-urlencoded" } }
).then(res => {console.log(res)});
};
If you remove your custom Content-Type header from the axios request, axios will send your data as JSON by default, and it will be parsed by your express JSON parser middleware.
axios.post("http://localhost:5000/user", { nameVal, nickNameVal })
.then(res => console.log(res));
The data you send to the server is nameVal and nickNameVal, so trying to access req.body.name will still give undefined. Try logging nameVal and nickNameVal instead.
app.post("/user", (req, res) => {
console.log(req.body.nameVal, req.body.nickNameVal);
});
According to axios documentation you need to pass an instance of URLSearchParams (or a query string of the parameters) as the second argument.
const params = new URLSearchParams();
params.append('nameVal', state.nameVal);
params.append('nickNameVal', state.nickNameVal);
axios.post('/user', params);

React-Adal returns 401 with POST request but 200 with GET request

I'm trying to send a POST request to an API that is hosted in Azure and is authenticated through Azure Active Directory. I'm using React with React-Adal to send my requests. I configured react-adal using the GitHub repo and this tutorial to guide me.
adalConfig.js
import { AuthenticationContext, adalFetch, withAdalLogin, adalGetToken } from 'react-adal';
export const adalConfig = {
tenant: 'ad5842d4-1111-1111-1111-111111111111',
clientId: '1f89aa20-1111-1111-1111-111111111111', //ClientID of the ReactClient application
endpoints: {
demoApi: 'e7926712-1111-1111-1111-111111111111', //ClientID of the DemoApi
microsoftGraphApi: 'https://graph.microsoft.com'
},
postLogoutRedirectUri: window.location.origin,
redirectUri: 'https://localhost:44394/',
cacheLocation: 'sessionStorage'
};
export const authContext = new AuthenticationContext(adalConfig);
export const adalDemoApiFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.demoApi, fetch, url, options);
export const adalTokenFetch = () =>
adalGetToken(authContext, adalConfig.endpoints.demoApi);
export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints);
When I use the adalDemoApiFetch with a GET request it works fine and returns 200 with the list of schedules.
const url = `https://localhost:44322/api/Schedules/GetAllSchedules`;
const response = await adalDemoApiFetch(axios.get, url);
console.log(response);
const schedules = response.data;
When I use the same adalDemoApiFetch with a POST to add a new schedule to the list it returns a 401.
const url = `https://localhost:44322/api/Schedules/AddSchedule`;
const azureADID = authContext.getCachedUser();
const token = authContext.acquireToken("e7926712-1111-1111-1111-111111111111");
console.log(token);
const options = {
beginningDateTime: this.state.begDateTime.toJSON(),
endindDateTime: this.state.endDateTime.toJSON(),
userID: this.state.userID,
azureADID: azureADID.profile.oid
};
const response = await adalDemoApiFetch(axios.post, url, options);
console.log(response);
I also tried copying out the token and using it in Postman to make the call and it still returns 401. When I use the token that is returned from the function below it works just fine.
export const adalTokenFetch = () =>
adalGetToken(authContext, adalConfig.endpoints.demoApi);
I use axios to call it in the code below and it works just fine.
const url = `https://localhost:44322/api/Schedules/AddSchedule`;
const azureADID = authContext.getCachedUser();
const token = await adalTokenFetch();
console.log(token);
const options = {
beginningDateTime: this.state.begDateTime.toJSON(),
endindDateTime: this.state.endDateTime.toJSON(),
userID: this.state.userID,
azureADID: azureADID.profile.oid
};
const response = await axios.post(url,
{
data: options
},
{
headers: {
"Authorization": `Bearer ${token}`
}
}
);
console.log(response);
What am I doing wrong? Why would it work with a GET request and not with the POST request? Am I missing something?
I tried working with Axios too. Seems that using axios it is unable to authorize the bearer token. Try using the Adalfetch that fixed the problem for me.
You need to tell adalApiFetch you are using POST method instead of GET. It defaults to GET, that is why that works ootb. Use the options object.

Resources