MERN - delete item/row in React Data Table Component - node.js

I have a MERN stack application that is modified from a great tutorial I completed. In the original app, transactions were rendered in a list populated from an API call to Mongo Atlas DB. I converted the list to a react-data-table-component and am now trying to figure out how to delete a table row/transaction. The original app had this as part of the transaction component with an onClick button. When I attempt to use the deleteTransaction function, I receive a "TypeError: Cannot read property '_id' of undefined". I can see that the data table renders via the object {transactions}, but cannot figure out why it does not recognize the _id.
Other info: state is managed through the React Context API, with a Router.js and Reducer.js.
TransactionTable.js
import React, { useContext, useEffect } from "react";
// Data table imports
import IconButton from "#material-ui/core/IconButton";
import DeleteIcon from "#material-ui/icons/Delete";
import Card from "#material-ui/core/Card";
import DataTable from "react-data-table-component";
// import transaction component and context provider
import { GlobalContext } from "../context/GlobalState";
// create data table component
export const TransactionTable = () => {
const { transactions, getTransactions, deleteTransaction } = useContext(
GlobalContext
);
// react-data-table-component Columns for back-end data
const columns = [
{
name: "Transaction",
selector: "text",
sortable: true
},
{
name: "Amount",
selector: "amount",
sortable: true,
// conditionally render amount if positive or negative
conditionalCellStyles: [
{
when: row => row.amount > 0,
style: {
color: "green"
}
},
{
when: row => row.amount < 0,
style: {
color: "red"
}
}
]
},
{
// where I'm attempting to pass the transactions prop and apply the deleteTransaction function
// using the delete button that renders in each row
cell: ({ transactions }) => (
<IconButton
aria-label="delete"
color="secondary"
onClick={() => deleteTransaction(transactions._id)}
>
<DeleteIcon />
</IconButton>
)
}
];
useEffect(() => {
getTransactions();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div>
<Card style={{ height: "100%" }} p={2} mx="auto">
<DataTable
title="Transactions"
columns={columns}
data={transactions}
defaultSortField="Transactions"
//actions={actions}
pagination={true}
highlightOnHover={true}
dense={true}
/>
</Card>
</div>
);
};
./controllers/transactions.js - this is where the deleteTransaction function is
const Transaction = require('../models/Transaction');
// #desc Get all transactions
// #route GET /api/v1/transactions
// #access Public
exports.getTransactions = async (req, res, next) => {
try {
const transactions = await Transaction.find();
//const result = result.transaction.toString()
return res.status(200).json({
success: true,
count: transactions.length,
data: transactions
});
} catch (err) {
return res.status(500).json({
success: false,
error: 'Server Error'
});
}
}
// #desc Add transaction
// #route POST /api/v1/transactions
// #access Public
exports.addTransaction = async (req, res, next) => {
try {
const { text, amount } = req.body;
const transaction = await Transaction.create(req.body);
return res.status(201).json({
success: true,
data: transaction
});
} catch (err) {
if(err.name === 'ValidationError') {
const messages = Object.values(err.errors).map(val => val.message);
return res.status(400).json({
success: false,
error: messages
});
} else {
return res.status(500).json({
success: false,
error: 'Server Error'
});
}
}
}
// #desc Delete transaction
// #route DELETE /api/v1/transactions/:id
// #access Public
exports.deleteTransaction = async (req, res, next) => {
try {
const transaction = await Transaction.findById(req.params.id);
if(!transaction) {
return res.status(404).json({
success: false,
error: 'No transaction found'
});
}
await transaction.remove();
return res.status(200).json({
success: true,
data: {}
});
} catch (err) {
return res.status(500).json({
success: false,
error: 'Server Error'
});
}
}

According to the docs https://www.npmjs.com/package/react-data-table-component#custom-cells, each cell is passed an object named row by convention (you can name it to whatever you want)..
This row object should have the _id you need..
// react-data-table-component Columns for back-end data
const columns = [
// ... column items,
{
cell: row => (
<IconButton
aria-label="delete"
color="secondary"
onClick={() => deleteTransaction(row._id)}
>
<DeleteIcon />
</IconButton>
)
}
]
Each row basically represents a single transaction.

Related

how to create poll using API with react functional component

this is my react js code and I want to connect with my node js API but I don't understand how to that ...!
import React, { useState } from "react";
import Poll from "react-polls";
// import "./styles.css";
/**
* https://stackoverflow.com/questions/65896319/react-js-class-poll-convert-into-react-hooks-poll
*/
// Declaring poll question and answers
const pollQuestion = "Youtube is the best place to learn ?";
const answers = [
{ option: "Yes", votes: 7 },
{ option: "No", votes: 2 },
{ option: "don't know", votes: 1 },
];
const Fakepolls = () => {
// Setting answers to state to reload the component with each vote
const [pollAnswers, setPollAnswers] = useState([...answers]);
// Handling user vote
// Increments the votes count of answer when the user votes
const handleVote = (voteAnswer) => {
setPollAnswers((pollAnswers) =>
pollAnswers.map((answer) =>
answer.option === voteAnswer
? {
...answer,
votes: answer.votes + 1,
}
: answer
)
);
};
return (
<div>
<Poll
noStorage
question={pollQuestion}
answers={pollAnswers}
onVote={handleVote}
/>
</div>
);
};
export default function App() {
return (
<div className="App">
<Fakepolls />
</div>
);
}
It work's fine with
// Declaring poll question and answers
const pollQuestion = "Youtube is the best place to learn ?";
const answers = [
{ option: "Yes", votes: 7 },
{ option: "No", votes: 2 },
{ option: "don't know", votes: 1 },
];
but I want to connect this poll with my API instead of Declaring it ..! this is my api- to get data -> ( router.get("/poll/:pollId", getPoll); //)
exports.getPoll = async (req, res, next) => {
try {
const { pollId } = req.params;
const polls = await Poll.findById(pollId);
if (!polls) throw new Error("no polls found");
res.status(200).json(polls);
} catch (error) {
error.status = 400;
next(error);
}
};
This is a postman image -
and this API for POST data- and my node js code -
exports.votes = async (req, res, next) => {
try {
/**
* 1. get the poll from db
* 2. check if the user already exists in any option
* 3. if user has already selected any option do nothing
* 4. if user has selected any other option remove from that option
* 5. if user does not exist in any option, insert his user id to selected option
*/
const { pollId } = req.params;
let { userId, answer } = req.body;
// get selected poll from db
const poll = await Poll.findById(pollId);
if (answer && poll) {
answer = answer.toLowerCase();
///Finf the Poll
let existingVote = null;
Object.keys(poll.options).forEach((option) => {
// loop on all options, check if the user already exists in any option
if (poll.options[option].includes(userId)) {
existingVote = option;
}
});
if (existingVote == null) {
// if there is no existing vote save it to db
try {
const push = {};
push[`options.${answer}`] = userId;
const update = await Poll.findByIdAndUpdate(
pollId,
{ $push: push },
{ upsert: true }
);
res.status(201).json(update);
} catch (err) {
error.status = 400;
next(error);
}
} else if (existingVote && existingVote.length > 0) {
// check if answer is same as previous, if yes send not modified
if (existingVote.toLowerCase() === answer.toLowerCase()) {
res.status(304).send("Response already saved");
} else {
// delete the previous response and save it in new
if (
Array.isArray(poll.options[existingVote]) &&
poll.options[existingVote].length > 0
) {
// TODO: filtering this is not returning array but 1
poll.options[existingVote] = poll.options[existingVote].filter(
(vote) => vote != userId
);
poll.options[answer] = poll.options[answer].push(userId);
const update = await Poll.findByIdAndUpdate(pollId, {
$set: { options: poll.options },
});
res.status(201).json(update);
}
}
} else {
error = {
status: 500,
message: "Something went wrong",
};
next(error);
}
} else {
error = {
status: 404,
message: "Poll not found",
};
next(error);
}
} catch (error) {
error.status = 400;
next(error);
}
};
this is a POSTMAN image using POST to store data --- >
how can I connect API with react poll
What you'd do is make a fetch() to your /api/polls endpoint inside your Fakepolls component, the URL being exactly as you show in your Postman screenshot. More info on fetch here at the MDN docs.
With the response you get from the endpoint, populate the answers array you component uses. From what I see, it would require a bit of transformation as your answer object is not quite the same as what Poll needs.
Next, upon user action, as well as updating the votes in the UI, you need to make another fetch to your vote endpoint.
Here's your component again with these adjustments. Keep in mind it's untested and the URLs are obviously not real:
import React, { useState, useEffect } from "react";
import Poll from "react-polls";
// import "./styles.css";
/**
* https://stackoverflow.com/questions/65896319/react-js-class-poll-convert-into-react-hooks-poll
*/
const Fakepolls = () => {
// Setting answers to state to reload the component with each vote
const [pollQuestion, setPollQuestion] = useState('');
const [pollAnswers, setPollAnswers] = useState([]);
// Query the actual poll info from the server
useEffect(() => {
fetch('http://your-server/api/polls/you-poll-id')
.then((response) => response.json()) //parse response as json
.then((pollObject) => {
let answerCountDictionary = Object.keys(pollObject.options)
.map(oKey => {
return {
option: oKey,
anwers: pollObject.options[oKey].length
}
}); //iterate over the 'options' properties' keys to get the names and the current votes
setPollAnswers(answerCountDictionary);
setPollQuestion(pollObject.question)
})
.catch((error) => {
console.error('Error:', error);
});
},[]) //adding empty array of dependencies to prevent multiple calls on state change
// Handling user vote
// Increments the votes count of answer when the user votes
const handleVote = (voteAnswer) => {
setPollAnswers((pollAnswers) =>
pollAnswers.map((answer) =>
answer.option === voteAnswer
? {
...answer,
votes: answer.votes + 1,
}
: answer
)
);
//also submit the backend
fetch('http://your-server/api/vote/poll-id', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: {
"userId": "the-logged-in-user",
"answer": voteAnswer
},
})
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
};
return (
<div>
<Poll
noStorage
question={pollQuestion}
answers={pollAnswers}
onVote={handleVote}
/>
</div>
);
};
export default function App() {
return (
<div className="App">
<Fakepolls />
</div>
);
}

React | Why the defaultValue of the input isn't being updated?

I have a stateful component that calls a CEP promise, to fetch data from post offices. This data is fetched when the Zip input is fulfilled with 9 chars - 8 number and an '-' - and return an object with desired information.
Heres the function:
const handleZipCode = useCallback(
async ({ target }: ChangeEvent<HTMLInputElement>) => {
const { value } = target;
try {
if (value.length === 9 && isDigit(value[8])) {
const zip = await cep(value);
if (zip?.city) {
setZipData(zip);
} else
addressFormRef.current?.setErrors({
user_zip_code: 'CEP not found',
});
}
} catch (e) {
addressFormRef.current?.setErrors({
user_zip_code: e.message ?? 'CEP not found',
});
}
},
[isDigit]
);
Then, on the return I have some fields, example:
<fieldset>
<legend>Address</legend>
<Input
mask=''
name='user_address'
placeholder='Rua um dois trĂªs'
defaultValue={zipData.street}
/>
</fieldset>
Here's the Input component:
const Input: React.FC<InputProps> = ({ name, ...rest }) => {
const { fieldName, defaultValue, registerField, error } = useField(name);
const inputRef = useRef(null);
useEffect(() => {
registerField({
name: fieldName,
ref: inputRef.current,
path: 'value',
// eslint-disable-next-line
setValue(ref: any, value: string) {
ref.setInputValue(value);
},
// eslint-disable-next-line
clearValue(ref: any) {
ref.setInputValue('');
},
});
}, [fieldName, registerField]);
return (
<Container>
<ReactInputMask ref={inputRef} defaultValue={defaultValue} {...rest} />
{error && <Error>{error}</Error>}
</Container>
);
};
However the zipData seems to update, but the default value is not fulfilled. What I'm doing wrong?
The default value will not change, as unform is an uncontrolled form library, the defaultValue will be set on the first render of the page and then will not change anymore.
To fix your problem you can do something like:
// on your handleZipCode function
formRef.current.setData({
zipData: {
street: zipResult.street,
},
});

Changing boolean value in API Mongo MERN React Node

I am getting error 404 that the API cannot be found. The Boolean in API is "auto_delete". I want to change it to true when this is referenced.
I wrote a button to reference a function that would reference this API but OnClick it will not be able to find this API apparently.
I posted first the front end file with constructor/binding, button, and function. I then put API at bottom.
Any input would help.
//FRONT END
This is the front end with button and function button references to
//CONSTRUCTOR AND BINDING FUNCTION
export default class VideoContent extends Component {
constructor(props) {
super(props);
let video = this.props.video;
let user = this.props.user;
this.state = {
fetchDuration: false,
viewed: false,
subscribed:
user &&
user.hasOwnProperty("follows") &&
user.follows.includes(video.author)
};
this.view = this.view.bind(this);
this.report = this.report.bind(this);
this.reportJ= this.reportJ.bind(this);
}
<Button
disabled={user ? !user.userId : true}
onClick={this.reportJ}
style={{ float: "right", backgroundColor: "purple" }}
>
ReportJ
</Button>
//FUNCTION
reportJ() {
const { video, user } = this.props;
confirmAlert({
title: "Confirm Objectionable Content",
message: "You Sure Fool?",
buttons: [
{
label: "Yes",
onClick: () => {
axios
.post(
inDev
? devAPI.concat("reportJ")
: baseAPI.concat("reportJ"),
{
id: video._id,
update: {
auto_delete: true
}
}
)
.then(res => {
this.setState({ changed: true });
})
.catch(error => {
console.log(error);
});
}
},
{
label: "No",
onClick: () => alert("Nevermind")
}
]
});
}
//SERVER JS File with API below
router.post("/reportJ", (req, res) => {
const { id, update } = req.body;
models.Videos.findById(id, function (err, video) {
video.auto_delete = true;
video.save((err, data) => {
if (err)
return res.json({
success: false,
error: err
});
return res.json({
success: true,
data: data
});
});
});
});

Stripe payment method. Use card [split-form] not working. Can't get card number expiry and cvc number

I am implementing stripe with react and node.
but I am not getting these values
<CardNumberElement/>
<CardExpiryElement/>
<CardCvcElement/>
Without its value can't get token and can't charge money.
if I only use cardElement that's work but cardElement is a one-line input field but I want to split it. so that's why I used CardNumberElement , CardExpiryElement, and CardCvcElement for splitting.
backend code is perfect but the error is inside frontend because we can't pass values perfectly for creating the stripe token for the payment.
Frontend Code:
import React from "react";
import { loadStripe } from "#stripe/stripe-js";
import {
Elements,
CardElement,
useStripe,
useElements,
CardNumberElement,
CardExpiryElement,
CardCVCElement,
injectStripe,
StripeProvider,
CardCvcElement
} from "#stripe/react-stripe-js";
import axios from "axios";
import { ServiceBooking } from "../../services/service-booking"
const CheckoutForm = ({ success }) => {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async event => {
event.preventDefault();
let number = elements.getElement(CardNumberElement);
let cvc = elements.getElement(CardCvcElement);
console.log("farrukh",number)
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: "card",
card: {
number: number,
exp_month: 4,
exp_year: 2021,
cvc: cvc,
}
});
if (!error) {
const { id } = paymentMethod;
try {
const data = await ServiceBooking.charge(id, 1099);
console.log(data);
success();
} catch (error) {
console.log(error);
}
}
};
return (
<form
onSubmit={handleSubmit}
style={{ maxWidth: "400px", margin: "0 auto" }}
>
<h2>Price: $10.99 USD</h2>
<CardNumberElement/>
<CardExpiryElement/>
<CardCvcElement/>
<button type="submit" disabled={!stripe}>
Pay
</button>
</form>
);
};
// you should use env variables here to not commit this
// but it is a public key anyway, so not as sensitive
const stripePromise = loadStripe("pk_test_wSvr6guTJvkKmv21jVqVd2D20049BVPKHP");
const Checkout = () => {
const [status, setStatus] = React.useState("ready");
if (status === "success") {
return <div>Congrats on your empanadas!</div>;
}
return (
<Elements stripe={stripePromise}>
<CheckoutForm
success={() => {
setStatus("success");
}}
/>
</Elements>
);
};
export default Checkout;
Backend Code:
router.post(
"/charge",
asyncHandler(async function (req, res) {
const { id, amount } = req.body;
try {
const payment = await stripe.paymentIntents.create({
amount,
currency: "USD",
payment_method: id,
confirm: true
});
console.log(payment);
return res.status(200).json({
confirm: "abc123"
});
} catch (error) {
console.log(error);
return res.status(400).json({
message: error.message
});
}
})
);
I have tried but could not charge (payment) successfully
in Stripe.js, you would just need to pass the CardElement to the stripe.createPaymentMethod call. You do not need the card number nor you can get the card number due to security reason.
let number = elements.getElement(CardNumberElement);
...
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: "card",
card: number
});
You may ask how do I pass in the cvc and expiry date? The answer is that you don't have to, Stripe.js will automatically locate the CVC and expiry input automatically in the same page.
See reference at https://stripe.com/docs/stripe-js/react
You need to change your front-end code. For a split form to work, you should use three different (card) keys under .createPaymentMethod to catch the CardNumberElement, CardExpiryElement, and CardCvcElement respectively
.createPaymentMethod(
{
type: 'card',
card: elements.getElement(CardNumberElement),
card: elements.getElement(CardExpiryElement),
card: elements.getElement(CardCvcElement),
}
Your Frontend Code should look like this:
import React from "react";
import { loadStripe } from "#stripe/stripe-js";
import {
Elements,
CardElement,
useStripe,
useElements,
CardNumberElement,
CardExpiryElement,
CardCVCElement,
injectStripe,
StripeProvider,
CardCvcElement
} from "#stripe/react-stripe-js";
import axios from "axios";
import { ServiceBooking } from "../../services/service-booking"
const CheckoutForm = ({ success }) => {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async event => {
event.preventDefault();
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardNumberElement),
card: elements.getElement(CardExpiryElement),
card: elements.getElement(CardCvcElement),
});
if (!error) {
const { id } = paymentMethod;
try {
const data = await ServiceBooking.charge(id, 1099);
console.log(data);
success();
} catch (error) {
console.log(error);
}
}
};
return (
<form
onSubmit={handleSubmit}
style={{ maxWidth: "400px", margin: "0 auto" }}
>
<h2>Price: $10.99 USD</h2>
<CardNumberElement/>
<CardExpiryElement/>
<CardCvcElement/>
<button type="submit" disabled={!stripe}>
Pay
</button>
</form>
);
};
// you should use env variables here to not commit this
// but it is a public key anyway, so not as sensitive
const stripePromise = loadStripe("pk_test_wSvr6guTJvkKmv21jVqVd2D20049BVPKHP");
const Checkout = () => {
const [status, setStatus] = React.useState("ready");
if (status === "success") {
return <div>Congrats on your empanadas!</div>;
}
return (
<Elements stripe={stripePromise}>
<CheckoutForm
success={() => {
setStatus("success");
}}
/>
</Elements>
);
};
export default Checkout;
Just for anyone coming up with this issue in 2022. I was having a similar set up as OP and the answer is here on YouTube. You can use the method .confirmCardPayment taking any of the elements. Something like:
(token) => {
stripe?.confirmCardPayment(token, {
payment_method: {
card: elements?.getElement(CardNumberElement),
billing_details: {
name: "Leandro",
},
},
});
}
it will still validate all fields. Testing in the sandbox, I was passing like so but testing with leaving CVC empty, and stripe API was rejecting.

Can't display data in localhost browser

I creating an app that stores data but when i finish the prompt input i get this error:
Here is my CptList.js
import React, { Component } from 'react';
import { Container, Button } from 'reactstrap';
import uuid from 'uuid';
export default class CpdList extends Component{
state = {}
handleClick = () => {
const date = prompt('Enter Date')
const activity = prompt('Enter Activity')
const hours = prompt('Enter Hours')
const learningStatement = prompt('Enter Learning Statement')
const evidence = prompt('YES! or NO!')
this.setState(state => ({
items: [
...state.items,
{
id: uuid(),
date,
activity,
hours,
learningStatement,
evidence
}
]
}));
}
render() {
const { items } = this.state;
return (
<Container>
<Button
color='dark'
style={{marginBottom: '2rem'}}
onClick={this.handleClick}
>Add Data</Button>
<Button
color='dark'
style={{marginBottom: '2rem'}}
onClick={() => { this.handleClick(items._id) }}
>Delete Data</Button>
</Container>
);
};
};
Can someone please tell me what im doing wrong? I am also having trouble with my delete function, this is my delete coding in my backend:
//Delete a Item
router.delete('/:id', (req, res) => {
Item.findById(req.params.id)
.then(item => item.remove().then(() => res.json({ success: true })))
.catch(err => res.status(404).json({ success: false }));
});
I think you have to initialize state with:
state = { items:[] }
The first time you add item to undefined empty list.
Moreover I think missing a state.items.map somewhere (at least for delete button)
state = [] // convert to array beacuse use map() or other javascipt method
this.setState(state => ({
items: [
// do not speard maybe
{
id: uuid(),
date,
activity,
hours,
learningStatement,
evidence
}
]
}));
plz write handleClick function
tell me working or not

Resources