#SOLVED [from first comment answer]
I am try to build paypal advance payment and card save during payment with nodejs. I successfully build paypal advance payment and store card in vault. but the problem is while I try to pay with previously saved card, it fails. Order is created successfully then the error occurred.
checkout html (ejs)
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css"
href="https://www.paypalobjects.com/webstatic/en_US/developer/docs/css/cardfields.css" />
<script src="https://www.paypal.com/sdk/js?components=buttons,hosted-fields&client-id=<%= clientId %>"
data-client-token="<%= clientToken %>"></script>
</head>
<body>
<div id="paypal-button-container" class="paypal-button-container"></div>
<div class="card_container">
<form id="card-form">
<label for="card-number">Card Number</label>
<div id="card-number" class="card_field"></div>
<div style="display: flex; flex-direction: row;">
<div>
<label for="expiration-date">Expiration Date</label>
<div id="expiration-date" class="card_field"></div>
</div>
<div style="margin-left: 10px;">
<label for="cvv">CVV</label>
<div id="cvv" class="card_field"></div>
</div>
</div>
<label for="card-holder-name">Name on Card</label>
<input type="text" id="card-holder-name" name="card-holder-name" autocomplete="off"
placeholder="card holder name" />
<div>
<label for="card-billing-address-street">Billing Address</label>
<input type="text" id="card-billing-address-street" name="card-billing-address-street" autocomplete="off"
placeholder="street address" />
</div>
<div>
<label for="card-billing-address-unit"> </label>
<input type="text" id="card-billing-address-unit" name="card-billing-address-unit" autocomplete="off"
placeholder="unit" />
</div>
<div>
<input type="text" id="card-billing-address-city" name="card-billing-address-city" autocomplete="off"
placeholder="city" />
</div>
<div>
<input type="text" id="card-billing-address-state" name="card-billing-address-state" autocomplete="off"
placeholder="state" />
</div>
<div>
<input type="text" id="card-billing-address-zip" name="card-billing-address-zip" autocomplete="off"
placeholder="zip / postal code" />
</div>
<div>
<input type="text" id="card-billing-address-country" name="card-billing-address-country" autocomplete="off"
placeholder="country code" />
</div>
<div>
<input type="checkbox" id="save" name="save">
<label for="save">Save your card</label>
</div>
<br /><br />
<button value="submit" id="submit" class="btn">Pay</button>
</form>
</div>
<script src="app.js"></script>
</body>
</html>
app.js
paypal
.Buttons({
// Sets up the transaction when a payment button is clicked
createOrder: function (data, actions) {
return fetch("/api/orders", {
method: "post"
// use the "body" param to optionally pass additional order information
// like product ids or amount
})
.then((response) => response.json())
.then((order) => order.id);
},
// Finalize the transaction after payer approval
onApprove: function (data, actions) {
return fetch(`/api/orders/${data.orderID}/capture`, {
method: "post",
})
.then((response) => response.json())
.then((orderData) => {
// Successful capture! For dev/demo purposes:
console.log(
"Capture result",
orderData,
JSON.stringify(orderData, null, 2)
);
const transaction = orderData.purchase_units[0].payments.captures[0];
alert(`Transaction ${transaction.status}: ${transaction.id}
See console for all available details
`);
// When ready to go live, remove the alert and show a success message within this page. For example:
// var element = document.getElementById('paypal-button-container');
// element.innerHTML = '<h3>Thank you for your payment!</h3>';
// Or go to another URL: actions.redirect('thank_you.html');
});
},
})
.render("#paypal-button-container");
// If this returns false or the card fields aren't visible, see Step #1.
if (paypal.HostedFields.isEligible()) {
let orderId;
// Renders card fields
paypal.HostedFields.render({
// Call your server to set up the transaction
createOrder: () => {
return fetch("/api/orders", {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
save: document.querySelector('#save').checked,
card_name: document.getElementById("card-holder-name").value,
billing_address: {
address_line_1: document.getElementById("card-billing-address-street").value,
address_line_2: document.getElementById("card-billing-address-unit").value,
admin_area_2: document.getElementById("card-billing-address-city").value,
admin_area_1: document.getElementById("card-billing-address-state").value,
postal_code: document.getElementById("card-billing-address-zip").value,
country_code: document.getElementById("card-billing-address-country").value
}
})
// use the "body" param to optionally pass additional order information like
// product ids or amount.
})
.then((res) => res.json())
.then((orderData) => {
console.log("==============>create order response<==============");
console.log(orderData);
orderId = orderData.id; // needed later to complete capture
return orderData.id;
});
},
styles: {
".valid": {
color: "green",
},
".invalid": {
color: "red",
},
},
fields: {
number: {
selector: "#card-number",
placeholder: "4111 1111 1111 1111",
},
cvv: {
selector: "#cvv",
placeholder: "123",
},
expirationDate: {
selector: "#expiration-date",
placeholder: "MM/YY",
},
},
}).then((cardFields) => {
document.querySelector("#card-form").addEventListener("submit", (event) => {
event.preventDefault();
cardFields
.submit({
save: document.querySelector('#save').checked,
// Cardholder's first and last name
cardholderName: document.getElementById("card-holder-name").value,
// Billing Address
billingAddress: {
// Street address, line 1
streetAddress: document.getElementById(
"card-billing-address-street"
).value,
// Street address, line 2 (Ex: Unit, Apartment, etc.)
extendedAddress: document.getElementById(
"card-billing-address-unit"
).value,
// State
region: document.getElementById("card-billing-address-state").value,
// City
locality: document.getElementById("card-billing-address-city")
.value,
// Postal Code
postalCode: document.getElementById("card-billing-address-zip")
.value,
// Country Code
countryCodeAlpha2: document.getElementById(
"card-billing-address-country"
).value,
},
})
.then(() => {
fetch(`/api/orders/${orderId}/capture`, {
method: "post",
})
.then((res) => res.json())
.then((orderData) => {
console.log("==============>capture order response<==============");
console.log(orderData);
// Two cases to handle:
// (1) Other non-recoverable errors -> Show a failure message
// (2) Successful transaction -> Show confirmation or thank you
// This example reads a v2/checkout/orders capture response, propagated from the server
// You could use a different API or structure for your 'orderData'
const errorDetail =
Array.isArray(orderData.details) && orderData.details[0];
if (errorDetail) {
var msg = "Sorry, your transaction could not be processed.";
if (errorDetail.description)
msg += "\n\n" + errorDetail.description;
if (orderData.debug_id) msg += " (" + orderData.debug_id + ")";
return alert(msg); // Show a failure message
}
// Show a success message or redirect
alert("Transaction completed!");
});
})
.catch((err) => {
alert("Payment could not be captured! " + JSON.stringify(err));
});
});
});
} else {
// Hides card fields if the merchant isn't eligible
document.querySelector("#card-form").style = "display: none";
}
paypal-api.js
import fetch from "node-fetch";
// set some important variables
const { CLIENT_ID, APP_SECRET } = process.env;
const base = "https://api-m.sandbox.paypal.com";
// call the create order method
export async function createOrder(body) {
const purchaseAmount = "100.00"; // TODO: pull prices from a database
const accessToken = await generateAccessToken();
const url = `${base}/v2/checkout/orders`;
const response = await fetch(url, {
method: "post",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: body ? JSON.stringify({
intent: "CAPTURE",
purchase_units: [
{
amount: {
currency_code: "USD",
value: purchaseAmount,
},
},
],
payment_source: {
card: {
name: body.card_name,
billing_address: body.billing_address,
attributes: {
vault: {
store_in_vault: 'ON_SUCCESS'
}
}
}
}
}) : undefined,
});
return handleResponse(response);
}
// capture payment for an order
export async function capturePayment(orderId) {
const accessToken = await generateAccessToken();
const url = `${base}/v2/checkout/orders/${orderId}/capture`;
const response = await fetch(url, {
method: "post",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
});
return handleResponse(response);
}
// generate access token
export async function generateAccessToken() {
const auth = Buffer.from(CLIENT_ID + ":" + APP_SECRET).toString("base64");
const response = await fetch(`${base}/v1/oauth2/token`, {
method: "post",
body: "grant_type=client_credentials",
headers: {
Authorization: `Basic ${auth}`,
},
});
const jsonData = await handleResponse(response);
return jsonData.access_token;
}
// generate client token
export async function generateClientToken() {
const accessToken = await generateAccessToken();
const response = await fetch(`${base}/v1/identity/generate-token`, {
method: "post",
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Language": "en_US",
"Content-Type": "application/json",
},
body: JSON.stringify({
customer_id: 'kxIaZbNwOZ'
})
});
const jsonData = await handleResponse(response);
return jsonData.client_token;
}
async function handleResponse(response) {
if (response.status === 200 || response.status === 201) {
return response.json();
}
const errorMessage = await response.text();
throw new Error(errorMessage);
}
server.js
import "dotenv/config";
import express from "express";
import * as paypal from "./paypal-api.js";
const app = express();
app.set("view engine", "ejs");
app.use(express.static("public"));
app.use(express.json());
// render checkout page with client id & unique client token
app.get("/", async (req, res) => {
const clientId = process.env.CLIENT_ID;
try {
const clientToken = await paypal.generateClientToken();
res.render("checkout", { clientId, clientToken });
} catch (err) {
res.status(500).send(err.message);
}
});
// create order
app.post("/api/orders", async (req, res) => {
try {
const order = await paypal.createOrder(req.body);
res.json(order);
} catch (err) {
res.status(500).send(err.message);
}
});
// capture payment
app.post("/api/orders/:orderID/capture", async (req, res) => {
const { orderID } = req.params;
try {
const captureData = await paypal.capturePayment(orderID);
res.json(captureData);
} catch (err) {
res.status(500).send(err.message);
}
});
app.listen(8888, () => console.log("App Started."));
Error:
{
"err": "Error: Shipping address requested for card payment\n...",
"timestamp": "1673098647388",
"referer": "www.sandbox.paypal.com",
"sdkCorrelationID": "04103b485b692",
"sessionID": "uid_82aff76087_mtm6mzu6mzk",
"clientID": "AUUbkZiQ1akU7ChP0UxbWN9U8-wR0pYmOUF8gzkXtsrcbn9GXDLB0fjOT14vG-rvWYwGmM7Lzj8LDK-C",
"env": "sandbox",
"buttonSessionID": "uid_05c2d7e1ec_mtm6mzc6mtq",
"buttonCorrelationID": "a17486c9208f5",
"time": "1673098647388",
"user_id": "uid_05c2d7e1ec_mtm6mzc6mtq",
"token": "2VY75292WT2863902"
}
Uncaught Error: Shipping address requested for card payment
at https://www.sandbox.paypal.com/smart/buttons?sdkVersion=5.0.344&style.layout=vertical&style.color=gold&style.shape=rect&style.tagline=false&style.menuPlacement=below&components.0=buttons&components.1=hosted-fields&locale.country=US&locale.lang=en&sdkMeta=eyJ1cmwiOiJodHRwczovL3d3dy5wYXlwYWwuY29tL3Nkay9qcz9jb21wb25lbnRzPWJ1dHRvbnMsaG9zdGVkLWZpZWxkcyZjbGllbnQtaWQ9QVVVYmtaaVExYWtVN0NoUDBVeGJXTjlVOC13UjBwWW1PVUY4Z3prWHRzcmNibjlHWERMQjBmak9UMTR2Ry1ydldZd0dtTTdMemo4TERLLUMiLCJhdHRycyI6eyJkYXRhLXVpZCI6InVpZF9laWVpbXFteW9tY3F4am1xeWd4dGxsZWlld2FvcmIifX0&clientID=AUUbkZiQ1akU7ChP0UxbWN9U8-wR0pYmOUF8gzkXtsrcbn9GXDLB0fjOT14vG-rvWYwGmM7Lzj8LDK-C&clientAccessToken=A21AALuiYv6v0yKLvGRKk4KoMModxjNd_Ph185k19xRLIjbptX_pGsMmYPvM9pTdyuKBwJbDm33J9zeRmkwl9_FbdGY1WtwIw&sdkCorrelationID=04103b485b692&storageID=uid_e93c1270fe_mtm6mzu6mzk&sessionID=uid_82aff76087_mtm6mzu6mzk&buttonSessionID=uid_05c2d7e1ec_mtm6mzc6mtq&env=sandbox&buttonSize=huge&fundingEligibility=eyJwYXlwYWwiOnsiZWxpZ2libGUiOnRydWUsInZhdWx0YWJsZSI6dHJ1ZX0sInBheWxhdGVyIjp7ImVsaWdpYmxlIjp0cnVlLCJwcm9kdWN0cyI6eyJwYXlJbjMiOnsiZWxpZ2libGUiOmZhbHNlLCJ2YXJpYW50IjpudWxsfSwicGF5SW40Ijp7ImVsaWdpYmxlIjpmYWxzZSwidmFyaWFudCI6bnVsbH0sInBheWxhdGVyIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXJpYW50IjpudWxsfX19LCJjYXJkIjp7ImVsaWdpYmxlIjp0cnVlLCJicmFuZGVkIjpmYWxzZSwiaW5zdGFsbG1lbnRzIjpmYWxzZSwidmVuZG9ycyI6eyJ2aXNhIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJtYXN0ZXJjYXJkIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJhbWV4Ijp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJkaXNjb3ZlciI6eyJlbGlnaWJsZSI6dHJ1ZSwidmF1bHRhYmxlIjp0cnVlfSwiaGlwZXIiOnsiZWxpZ2libGUiOmZhbHNlLCJ2YXVsdGFibGUiOmZhbHNlfSwiZWxvIjp7ImVsaWdpYmxlIjpmYWxzZSwidmF1bHRhYmxlIjp0cnVlfSwiamNiIjp7ImVsaWdpYmxlIjpmYWxzZSwidmF1bHRhYmxlIjp0cnVlfX0sImd1ZXN0RW5hYmxlZCI6ZmFsc2V9LCJ2ZW5tbyI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJpdGF1Ijp7ImVsaWdpYmxlIjpmYWxzZX0sImNyZWRpdCI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJhcHBsZXBheSI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJzZXBhIjp7ImVsaWdpYmxlIjpmYWxzZX0sImlkZWFsIjp7ImVsaWdpYmxlIjpmYWxzZX0sImJhbmNvbnRhY3QiOnsiZWxpZ2libGUiOmZhbHNlfSwiZ2lyb3BheSI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJlcHMiOnsiZWxpZ2libGUiOmZhbHNlfSwic29mb3J0Ijp7ImVsaWdpYmxlIjpmYWxzZX0sIm15YmFuayI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJwMjQiOnsiZWxpZ2libGUiOmZhbHNlfSwiemltcGxlciI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJ3ZWNoYXRwYXkiOnsiZWxpZ2libGUiOmZhbHNlfSwicGF5dSI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJibGlrIjp7ImVsaWdpYmxlIjpmYWxzZX0sInRydXN0bHkiOnsiZWxpZ2libGUiOmZhbHNlfSwib3h4byI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJtYXhpbWEiOnsiZWxpZ2libGUiOmZhbHNlfSwiYm9sZXRvIjp7ImVsaWdpYmxlIjpmYWxzZX0sImJvbGV0b2JhbmNhcmlvIjp7ImVsaWdpYmxlIjpmYWxzZX0sIm1lcmNhZG9wYWdvIjp7ImVsaWdpYmxlIjpmYWxzZX0sIm11bHRpYmFuY28iOnsiZWxpZ2libGUiOmZhbHNlfSwic2F0aXNwYXkiOnsiZWxpZ2libGUiOmZhbHNlfX0&platform=desktop&experiment.enableVenmo=false&experiment.enableVenmoAppLabel=false&flow=purchase¤cy=USD&intent=capture&commit=true&vault=false&renderedButtons.0=paypal&renderedButtons.1=paylater&debug=false&applePaySupport=false&supportsPopups=true&supportedNativeBrowser=false&experience=&allowBillingPayments=true:1492:261589
at n.dispatch (https://www.sandbox.paypal.com/smart/buttons?sdkVersion=5.0.344&style.layout=vertical&style.color=gold&style.shape=rect&style.tagline=false&style.menuPlacement=below&components.0=buttons&components.1=hosted-fields&locale.country=US&locale.lang=en&sdkMeta=eyJ1cmwiOiJodHRwczovL3d3dy5wYXlwYWwuY29tL3Nkay9qcz9jb21wb25lbnRzPWJ1dHRvbnMsaG9zdGVkLWZpZWxkcyZjbGllbnQtaWQ9QVVVYmtaaVExYWtVN0NoUDBVeGJXTjlVOC13UjBwWW1PVUY4Z3prWHRzcmNibjlHWERMQjBmak9UMTR2Ry1ydldZd0dtTTdMemo4TERLLUMiLCJhdHRycyI6eyJkYXRhLXVpZCI6InVpZF9laWVpbXFteW9tY3F4am1xeWd4dGxsZWlld2FvcmIifX0&clientID=AUUbkZiQ1akU7ChP0UxbWN9U8-wR0pYmOUF8gzkXtsrcbn9GXDLB0fjOT14vG-rvWYwGmM7Lzj8LDK-C&clientAccessToken=A21AALuiYv6v0yKLvGRKk4KoMModxjNd_Ph185k19xRLIjbptX_pGsMmYPvM9pTdyuKBwJbDm33J9zeRmkwl9_FbdGY1WtwIw&sdkCorrelationID=04103b485b692&storageID=uid_e93c1270fe_mtm6mzu6mzk&sessionID=uid_82aff76087_mtm6mzu6mzk&buttonSessionID=uid_05c2d7e1ec_mtm6mzc6mtq&env=sandbox&buttonSize=huge&fundingEligibility=eyJwYXlwYWwiOnsiZWxpZ2libGUiOnRydWUsInZhdWx0YWJsZSI6dHJ1ZX0sInBheWxhdGVyIjp7ImVsaWdpYmxlIjp0cnVlLCJwcm9kdWN0cyI6eyJwYXlJbjMiOnsiZWxpZ2libGUiOmZhbHNlLCJ2YXJpYW50IjpudWxsfSwicGF5SW40Ijp7ImVsaWdpYmxlIjpmYWxzZSwidmFyaWFudCI6bnVsbH0sInBheWxhdGVyIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXJpYW50IjpudWxsfX19LCJjYXJkIjp7ImVsaWdpYmxlIjp0cnVlLCJicmFuZGVkIjpmYWxzZSwiaW5zdGFsbG1lbnRzIjpmYWxzZSwidmVuZG9ycyI6eyJ2aXNhIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJtYXN0ZXJjYXJkIjp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJhbWV4Ijp7ImVsaWdpYmxlIjp0cnVlLCJ2YXVsdGFibGUiOnRydWV9LCJkaXNjb3ZlciI6eyJlbGlnaWJsZSI6dHJ1ZSwidmF1bHRhYmxlIjp0cnVlfSwiaGlwZXIiOnsiZWxpZ2libGUiOmZhbHNlLCJ2YXVsdGFibGUiOmZhbHNlfSwiZWxvIjp7ImVsaWdpYmxlIjpmYWxzZSwidmF1bHRhYmxlIjp0cnVlfSwiamNiIjp7ImVsaWdpYmxlIjpmYWxzZSwidmF1bHRhYmxlIjp0cnVlfX0sImd1ZXN0RW5hYmxlZCI6ZmFsc2V9LCJ2ZW5tbyI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJpdGF1Ijp7ImVsaWdpYmxlIjpmYWxzZX0sImNyZWRpdCI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJhcHBsZXBhe
Expecting the payment success
Related
As you can see in the React Js code I am trying the Reduce one by one quantity when clicking the Delivered button. When the Click Delivered button quantity becomes null.
const reduceQuantity=(productId)=>{
const quantityReduce=parseInt(product.quantity )- 1
const url = `http://localhost:5000/product/${productId}`
fetch(url, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ quantityReduce }),
})
.then((res) => res.json())
.then((data) => {
console.log('success', data)
});
return (
<div className='item-container'>
<div className='text-center'>
<img src={product.picture} alt="" />
</div>
<h6><b>{product.name}</b></h6>
<p className='text-justify'><b>Description:</b>{product.Description}</p>
<div className='d-flex'>
<div>
<p><b>Price:</b>{product.price}</p>
<p><b>supplierName:</b>{product.supplierName}</p>
</div>
<p><b>Quantity:</b>{product.quantity}</p>
</div>
</div>`enter code here`
<div className='text-center d-flex justify-content-around'>
<button className='btn btn-outline-success rounded-pill' >Delivered</button>
server Side API:
// update quantity of products
app.put("/product/:id", async (req, res) => {
const id = req.params.id;
const data = req.body;
console.log("from update api", data);
const filter = { _id: ObjectId(id) };
const options = { upsert: true };
const updateDoc = {
$set: {
quantity: data.addQuantity
},
};
My Goal for this one is to Add ObjectId inside the array
In my backend Im declare schema on this code:
tchStudents: [{
type: Schema.Types.ObjectId,
ref: "Student"
}]
THen Im do adding an ObjectId to insert to the array of ObjectID:
My BackEnd is very fine
router.put('/assignAddStudents/:tchID', async (req,res) => {
try {
const searchTch = await Teacher.findOne({ tchID: req.params.tchID })
if(!searchTch){
return res.status(404).send({
success: false,
error: 'Teacher ID not found'
});
} else {
let query = { tchID: req.params.tchID }
let assignedStudentObjID = {$push:{tchStudents: req.body.tchStudents }}
Teacher.updateOne(query, assignedStudentObjID ,() => {
try{
return res.status(200).send({
success: true,
msg: 'Student ID has been assigned'
});
} catch(err) {
console.log(err);
return res.status(404).send({
success: false,
error: 'Teacher ID not found'
})
}
})
}
} catch (err) {
console.log(err)
}
})
But my Front End Not working
err: BAD REQUEST(400) Unexpected token " in JSON at position 0
import React, {useState} from 'react'
import axios from 'axios'
import { URL } from '../../utils/utils'
import { Modal, Button } from 'react-materialize';
import ListTchStudents from '../lists/ListTchStudents';
const trigger =
<Button
style={{marginLeft:'2rem'}}
tooltip="Add More..."
tooltipOptions={{
position: 'top'
}}
className="btn-small red darken-4">
<i className="material-icons center ">add_box</i>
</Button>;
const MdlAddStudents =({teacher}) => {
const [data, setData] = useState('');
const { tchStudents} = data;
const {
tchID,
} = teacher; // IF WE RENDER THIS IT TURNS INTO OBJECT
const assignedStudent = () => {
// BUT WE SENT IT TO THE DATABASE CONVERT TO JSON.STRINGIFY to make ObjectId
const requestOpt = {
method: 'PUT',
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify(data)
}
axios.put(`${URL}teachers/assignAddStudents/${tchID}`, data,requestOpt)
.then(res => {
setData(res.data.data)
})
}
return (
<Modal header="Add Students" trigger={trigger}>
Please ADD and REMOVE Student ID No. for {tchID}
<div>
<ul
style={{marginBottom:'2rem'}}
className="collection">
{
Object.values(teacher.tchStudents).map(tchStudent => {
return(
<ListTchStudents
tchStudent={tchStudent}
/>
);
})
}
</ul>
<div className="row">
<div className="col s6 offset-s3"></div>
<div className="input-field">
<label
htmlFor=""
className="active black-text"
style={{fontSize:'1.3rem'}}>
Add Students here:
</label>
<input
type="text"
name="tchStudents"
value={(tchStudents)}
className="validate"
onChange={(e) => setData(e.target.value)}
/>
</div>
</div>
</div>
{/* BUT WE SENT IT TO THE DATABASE CONVERT TO JSON.STRINGIFY to send ObjectId to the database
*/}
<div className="row">
<div className="col s2 offset-s3" ></div>
<Button
onClick={assignedStudent}
tooltip="Add Students"
tooltipOptions={{
position: 'right'
}}
className="btn green darken-3">
<i className="material-icons ">add_circle</i>
</Button>
</div>
<p>There are {Object.values(teacher.tchStudents).length} student/s
assigned</p>
</Modal>
)
}
// MdlAddStudents.propTypes = {
// assignedStudents: PropTypes.func.isRequired,
// }
export default MdlAddStudents;
// export default connect(null, (assignedStudents))(MdlAddStudents);
Thank you for helping out
The problem stems from you attempting to wrap your tchStudents state property in an object named data.
My advice is to keep it very simple
// it's just a string
const [tchStudents, setTchStudents] = useState("")
const assignedStudent = () => {
// create your request payload
const data = { tchStudents }
// no config object required
axios.put(`${URL}teachers/assignAddStudents/${tchID}`, data)
.then(res => {
// not sure what you want to do here exactly but
// `res.data` should look like
// { success: true, msg: 'Student ID has been assigned' }
setTchStudents("") // clear the input ¯\_(ツ)_/¯
})
}
The only other change is to use the new setter name in your <input>...
<input
type="text"
name="tchStudents"
value={tchStudents}
className="validate"
onChange={(e) => setTchStudents(e.target.value)}
/>
I'm receiving this error: Error: bad content-type header, unknown content-type: text/plain;charset=UTF-8 when trying to upload a photo using Formidable. When I console.log fields and files, I receive two empty objects.
What do I need to change to solve this and upload the photo?
CreatePost.js
const CreatePost = () => {
const [values, setValues] = useState({
title: "",
body: "",
photo: "",
error: "",
createdPost: "",
formData: "",
});
const { user, token } = isAuthenticated();
const {
title,
body,
error,
createdPost,
formData,
} = values;
const handleChange = (name) => (event) => {
const value = name === "photo" ? event.target.files[0] : event.target.value;
setValues({ ...values, [name]: value, formData: new FormData() });
};
const clickSubmit = (event) => {
event.preventDefault();
setValues({ ...values, error: "" });
createPost(user._id, token, formData).then((data) => {
if (data.error) {
setValues({ ...values, error: data.error });
} else {
setValues({
...values,
title: "",
body: "",
photo: "",
createdPost: data.title,
});
}
});
};
const newPostForm = () => (
<form className="mb-3" onSubmit={clickSubmit}>
<h4>Post Photo</h4>
<div className="form-group">
<label className="btn btn-secondary">
<input
onChange={handleChange("photo")}
type="file"
name="photo"
accept="image/*"
/>
</label>
</div>
<div className="form-group">
<label className="text-muted">Title</label>
<input
onChange={handleChange("title")}
type="text"
className="form-control"
value={title}
/>
</div>
<div>
<label>Post body</label>
<textarea
onChange={handleChange("body")}
value={body}
/>
</div>
<button>Create Post</button>
</form>
);
const showError = () => (
<div
style={{ display: error ? "" : "none" }}>
{error}
</div>
);
const showSuccess = () => (
<div
style={{ display: createdPost ? "" : "none" }}>
<h2>{`${createdPost} is created!`}</h2>
</div>
);
return (
<div>
<div>
{showSuccess()}
{showError()}
{newPostForm()}
</div>
</div>
);
};
export default CreatePost;
API request (create post)
export const createPost = (userId, post, token) => {
return fetch(`${API}/blog/post/${userId}`, {
method: 'POST',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${token}`
},
body: post
})
.then(response => {
return response.json();
})
.catch(err => {
console.log(err);
});
};
controllers/posts.js
exports.create = (req, res) => {
let form = new formidable()
form.keepExtensions = true
form.parse(req, (err, fields, files) => {
if(err) {
console.log(err)
return res.status(400).json({
error: 'Image could not be uploaded'
})
}
const { title, body } = fields
console.log(fields)
if (!title || !body) {
return res.status(400).json({
error: "All fields are required"
})
}
let post = new Post(fields)
if(files.photo) {
if (files.photo.size > 1000000) {
return res.status(400).json({
error: "Image should be less than 1MB in size."
})
}
post.photo.data = fs.readFileSync(files.photo.path)
post.photo.contentType = files.photo.type
}
post.save((err, result) => {
if(err) {
return res.status(400).json({
error: errorHandler(err)
})
}
res.json(result)
})
})
}
exports.photo = (req, res, next) => {
if (req.post.photo.data) {
res.set('Content-Type', req.post.photo.contentType)
return res.send(req.post.photo.data)
}
next()
}
this has been up for a long time, I bet you already solve it.
If somebody stumbles with this question as I did, what worked for me was adding .IncomingForm() method on the new form in post controller file, like this:
let form = new formidable.IncomingForm();
I'm trying to build a web app that uses Spotify API now. I want it to send a search keyword that an user submits to the server and send back its search result to the front end. The problem is I get a 404 status code for the fetch call. The POST request works fine.
Main.js
import React, { Component } from "react";
import SingerCard from "./SingerCard";
import axios from "axios";
export class Main extends Component {
constructor(props) {
super(props);
this.state = {
keyword: "",
artists: [],
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({ keyword: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
axios
.post(
"http://localhost:4000/search_result",
{
keyword: this.state.keyword,
},
{
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
)
.then(function (res) {
console.log(res);
})
.catch(function (err) {
console.log(err);
});
}
componentDidMount() {
fetch("http://localhost:4000/api")
.then((res) => res.json)
.then((artists) => {
this.setState({ artists });
});
}
render() {
return (
<div className="main">
<form onSubmit={this.handleSubmit}>
<label htmlFor="search">Search an artist: </label>
<span>
<input
type="search"
value={this.state.keyword}
onChange={this.handleChange}
name="keyword"
/>
<button type="submit" value="Submit">
Search
</button>
</span>
</form>
<br />
<div className="container">
{this.state.artists.map((elem) => (
<SingerCard
images={elem.images}
name={elem.name}
artists={this.state.artists}
/>
))}
{console.log(this.state.artists)}
</div>
<br />
</div>
);
}
}
export default Main;
server.js
const express = require("express");
const SpotifyWebApi = require("spotify-web-api-node");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
const port = 4000 || process.env.PORT;
require("dotenv").config();
app.use(express.json());
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
// Create the api object with the credentials
var spotifyApi = new SpotifyWebApi({
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
});
// Retrieve an access token.
spotifyApi.clientCredentialsGrant().then(
function (data) {
console.log("The access token expires in " + data.body["expires_in"]);
console.log("The access token is " + data.body["access_token"]);
// Save the access token so that it's used in future calls
spotifyApi.setAccessToken(data.body["access_token"]);
},
function (err) {
console.log("Something went wrong when retrieving an access token", err);
}
);
app.post("/search_result", (req, res) => {
console.log(req.body.keyword);
spotifyApi.searchArtists(req.body.keyword).then(function (data) {
var search_res = data.body.artists.items;
res.json(search_res);
app.get("http://localhost:/api", (req, res) => {
res.json(search_res);
res.end();
});
res.end();
}),
function (err) {
console.log(err);
};
});
app.listen(port, () => console.log(`It's running on port ${port}`));
I think the app.get() in the app.post() causes the error but I can't figure out another way to send the search result back.
You're getting a 404 because the get method is not correctly defined.
Update your server code to define the get method to just keep the pathname, like this:
app.get("/api", (req, res) => {
// ...
}
Currently, you are defining this route inside the app.post. The get route definition should be outside of the post route.
Use Axios.get
import React, { Component } from "react";
// import SingerCard from "./SingerCard";
import axios from "axios";
export class Main extends Component {
constructor(props) {
super(props);
this.state = {
keyword: "",
artists: []
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({ keyword: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
const headers = {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
};
axios.post(
"https://jsonplaceholder.typicode.com/users",
{ keyword: this.state.keyword },
{ headers: headers }
)
.then(res => {
console.log(res.data);
})
.catch(err => {
console.log(err);
});
}
componentDidMount() {
axios.get("https://jsonplaceholder.typicode.com/users").then(res => {
this.setState({
artists: res.data
});
});
}
render() {
return (
<div className="main">
<form onSubmit={this.handleSubmit}>
<label htmlFor="search">Search an artist: </label>
<span>
<input
type="search"
value={this.state.keyword}
onChange={this.handleChange}
name="keyword"
/>
<button type="submit" value="Submit">
Search
</button>
</span>
</form>
<br />
<div className="container">
{this.state.artists.map(elem => (
<div key={elem.id}>
<ul>
<li>{elem.name}</li>
</ul>
</div>
))}
</div>
</div>
);
}
}
export default Main;
I'm trying to build a website where user post queries and other users are able to answer in it. I'm trying to implement various tags in it using react-tags-input module. However i'm having problem in sending those tags to node server. Help needed.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Link } from 'react-router-dom';
import '../Login.css';
import "./Home.css";
import Post from "./Post";
import { WithContext as ReactTags } from 'react-tag-input';
const KeyCodes = {
comma: 188,
enter: 13,
};
const delimiters = [KeyCodes.comma, KeyCodes.enter];
class query extends Component {
constructor() {
super();
this.state = {
userquery:'',
queries:[],
tags: [],
suggestions: [
{ id: "Javascript", text: "Javascript" },
{ id: "Java", text: "Java" },
{ id: 'node.js', text: 'node.js' },
{ id: 'react.js', text: 'react.js' },
{ id: 'express', text: 'express' },
{ id: 'bootstrap', text: 'bootstrap' },
{ id: 'python', text: 'python' },
{ id: 'c++', text: 'c++' }
]
};
this.handleDelete = this.handleDelete.bind(this);
this.handleAddition = this.handleAddition.bind(this);
this.handleDrag = this.handleDrag.bind(this);
}
onChange = (e) => {
const state = this.state
state[e.target.name] = e.target.value;
this.setState(state);
}
componentDidMount(){
fetch('/query').
then((Response)=>Response.json()).
then(data =>{
this.setState({queries:data.reverse()});
})
}
handleDelete(i) {
const { tags } = this.state;
this.setState({
tags: tags.filter((tag, index) => index !== i),
});
}
handleAddition(tag) {
this.setState(state => ({ tags: [...state.tags, tag] }));
}
handleDrag(tag, currPos, newPos) {
const tags = [...this.state.tags];
const newTags = tags.slice();
newTags.splice(currPos, 1);
newTags.splice(newPos, 0, tag);
// re-render
this.setState({ tags: newTags });
}
render() {
const {userquery } = this.state;
const { tags, suggestions } = this.state;
return (
<div class="container">
<form action="/queries" method="POST">
<h2 class="form-signin-heading" color="blue">Want to ask something? ask here!</h2>
<label for="inputQuery" class="sr-only">query</label>
<textarea type="text" class="form-control" placeholder="want to ask something? ask here!" name="userquery" required/>
<br/>
<div class="form-group ">
<input class="form-control" type="text" name="image" placeholder="image url"/>
</div>
<div class="form-group ">
<ReactTags
name='tags'
tags={tags}
suggestions={suggestions}
handleDelete={this.handleDelete}
handleAddition={this.handleAddition}
handleDrag={this.handleDrag}
delimiters={delimiters}
/>
</div>
<br/>
<button class="btn btn-lg btn-primary btn-block" >Ask</button>
</form>
<section>
<h2> Recent Posts</h2>
</section>
{this.state.queries.map((item, key) => {
return (<Post item={item} key={key} />)
}
)
}
</div>
);
}
}
export default query;
server file - I'm able to get userquery and image but req.body.tags is returning an empty object.
app.post("/queries",isLoggedIn,function(req,res){
var postQuery =req.body.userquery;
var userImages =req.body.image;
console.log(req.body.tags);
username=req.user.username;
var newQuery = {
name:username,
image:userImages,
description:postQuery
}
Query.create(newQuery,function(err,newlyCreated){
if(err){
console.log(err);
}
else{
res.redirect("http://localhost:3000/home");
}
})
// res.send("you hit the post route")
});
Edited
onSubmit = e => {
e.preventDefault()
const {someText, tags} = this.state
const data = {someText, tags: tags.map(x => x.id)}
alert(`Submitting: ${JSON.stringify(data)}`)
fetch(
'queries',
{
method: 'POST',
mode: "cors", // no-cors, cors, *same-origin
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "same-origin",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
},
)
}
server.js
app.post("/queries",function(req,res){
console.log("tags are:-",req.body)
});
output
tags are:- {}
The problem is in react-tags storing selected tags as a bunch of span elements, not input. So, these values are not included in form data being submitted.
You have to handle form submit logic manually.
Here is an example of handlers you need to add/change:
onSubmit = e => {
e.preventDefault() // stop default form submission
const { field1, field2, tags } = this.state // select form input values to submit
const data = { field1, field2, tags: tags.map(t => t.id) } // create an object to submit
fetch(
'url',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
},
) // send POST request
}
onChange = e => this.setState({ [e.target.name]: e.target.value }) // for plain inputs
onAdd = tag => this.setState(state => ({ tags: [...state.tags, tag] })) // for tags input
render() {
return (
<form onSubmit={this.onSubmit}>
<input name="field1" onChange={this.onChange} />
<input name="field2" onChange={this.onChange} />
<ReactTags
tags={this.state.tags}
handleAddition={this.onAdd}
/>
<button type="submit">submit</button>
</form>
)
}
Working example is in this sandbox.
Result is here: https://prnt.sc/kg2j9p
Update: now codesandbox uses fetch and posts the form data, you can try to submit form and see outgoing request in network tab in browser dev tools.