PayPal Buttons don't close (Duplicates after re-rendering) - reactjs

I'm implementing the PayPal Smart Payment Buttons with React, and every time my component re-renders I receive a duplicate of the buttons (with the one on the bottom holding the correct transaction information).
Clearly I need to close the buttons, if I try so I receive the error that window.paypal.close()is not a function.
I tried to follow this example: Paypal React shows extra buttons after changing amount
Here is my code, I'm using Redux for state management and I need to rerender the component if items in the shopping cart are removed (to update the item information of the transaction):
useEffect(() => {
if (window.myButton) {
window.myButton.close()
}
window.myButton = window.paypal
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: "test transaction",
amount: {
currency_code: "USD",
value: document.getElementById("totalAmount").innerHTML,
breakdown: {
item_total: {
currency_code: "USD",
value: document.getElementById("totalAmount").innerHTML
}
}
}
,
items: itemsInCart.map(item => {
console.log(item.value)
return {
name: item.name,
unit_amount: {
currency_code: "USD",
value: String(item.price)
},
quantity: "1"
}
})
}
]
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
}
.catch(function(error) {
console.error("Error writing document: ", error);
});
},
onError: err => {
// setError(err);
console.error(err);
}
})
.render(paypalRef.current)
}, [itemsInCart]);

})
.render(paypalRef.current)
The problem is you are setting myButton to the .render() promise result, not the Button itself.
You need to store a reference to the actual Button (before rendereing it), and only then .render() it -- so that later you can call .close() on the reference. Basically:
let myButton = paypal.Buttons(
....
});
myButton.render(paypalRef.current)
// and at some later point in time...
myButton.close();

Related

ReactJS: axios POST with recursive function

CreateFolder.js
Hi!! I'm trying loop an js tree object to create a folder for the childs elements with de Egnyte API.
I think I have posed the problem wrong.
My recursive funcion is cheking if the parent have childs, if this true, loop his childs, if the child isn't a parent, i want to create the folder with this child id.
The problem is when I see the console, first there are all prints of the '-----ADD-----', after that, there are all prints of the '-----ADDED' so I don't understant why they aren't simultaneously
The API works fine, this create my 6 folders, the problem of this code is when I loop my original TREE that have 400 childs, and I need to create this 400 folders but the callbacks doesn't work, this creates about 60 folders, and there are much 403 errors. I think there are much calls in a short time.
I tried to call the function addFolder with a time out or doing the function async but doesn't work too.
I explained my problem well? Can someone help me?
Thank you so much!!
import { ConnectingAirportsOutlined } from "#mui/icons-material"
import axios from "axios"
import { useEffect, useState } from "react"
import { useSelector } from "react-redux"
export default function CreateFolders() {
console.log('======CreateFolders=====')
// const state = useSelector(state => state)
// const tree = state.assets.tree
const tree = [
{
id: '1000',
name: 'GRUPO GENERADORES',
child: [
{
id: '1100',
name: 'MOTORES',
child: [
{
id: '1100.1',
name: 'MOTOR 1'
},
{
id: '1100.2',
name: 'MOTOR 2'
},
{
id: '1100.3',
name: 'MOTOR 3'
},
]
},
{
id: '1200',
name: 'ALTERNADORES',
child: [
{
id: '1200.1',
name: 'ALTERNADOR 1'
},
{
id: '1200.2',
name: 'ALTERNADOR 2'
},
{
id: '1200.3',
name: 'ALTERNADOR 3'
}
]
}
]
}
]
useEffect(() => {
getTreeItems(tree)
}, [tree])
const api = 'https://test.egnyte.com/pubapi/v1/fs/Shared/test/'
const headers = {
"headers": {
"Authorization": "Bearer XXXXXXXXXX",
"Content_type": "application/json"
}
}
const body = {
"action": "add_folder"
}
const addFolder = async (id) => {
const endpoint = api + id
await axios.post(endpoint, body, headers).then(res => {
console.log('OK')
return
}).catch((error) => {
console.log('ERROR')
})
}
const getTreeItems = treeItems => {
return treeItems.map(item => {
if (item.child && item.child.length > 0) {
getTreeItems(item.child)
}
if (item.id.includes('.')) {
console.log('-------ADD-------')
console.log(item.id)
addFolder(item.id)
console.log('-------ADDED')
}
})
}
return (
<>
<h2>CreateFolders</h2>
</>
)
}
The description you provided seems like you reached the rate limit (per second/daily).
Try to explore the response headers in failed requests to get more information about:
What kind of quotas have you exceeded (per second/daily)
When you can retry the request (e.g Retry-After header)
There are 2 solutions you can try:
Use bulk operation (create all the entities by single request) if the API supports it. This is a preferable solution
Retry the request after the timeout you get from the failed request header (e.g Retry-After header)
I solved my problem with the setTimeout with an index, because my function was executing the whole thing after 3 seconds, and with this index, add more time to every item, and executes each item 3 seconds after the previous one
My function after:
folders.map((id) => {
setTimeout(() => {
addFolder(id)
}, 3000)
})
My function before:
folders.map((id, index) => {
setTimeout(() => {
addFolder(id)
}, 3000 * index)
})
It's silly but I didn't know how the setTimeout worked

xstate pass event data into onDone from service

I have xstate react script whereby a user fills in a form, and presses submit. On submit the xstate received a send("VALIDATE", {formData}) and that is run through a service that validates the form. On success the script transitions to the target: "success" and i need that final "success" state to call an external function which actually does the saving of the script.
I can get the data into the validator function, BUT, after the onDone, the subsequent success state doesn't appear to see the data.
How can I wire the data from the validating event to the success event??
id: 'validator',
initial: 'populating',
context: {},
states: {
populating: {
on: {
VALIDATE: 'validating'
}
},
validating: {
invoke: {
src: (context, data) => doValidate(data),
onDone: {
target: 'success',
actions: assign({ data: "hello world"})
},
onError: 'failure'
}
},
success: {
invoke: {
// I do see the hello world here, but what I want is the 'data' from the doValidate(data)
src: (ctx)=>{console.log("invoked success, what can I see here: ", ctx)}
}
},
I'm triggering the validate via: send("VALIDATE", formData)
If I understood you correctly you want the event from the first service onDoneto be available to the second service?
The easiest way would be to put the data on the context and access it in the other state/service and delete it afterward.
Or you can model your machine to send custom event to itself when the first
service is done.
import { createMachine, assign, send, interpret } from "xstate";
const machine = createMachine({
preserveActionOrder: true, //make sure actions are executed in order
id: "validator",
initial: "populating",
context: {},
states: {
populating: {
on: {
VALIDATE: "validating"
}
},
validating: {
on: {
// Listen to the event that fires when the first service is done
FIRST_SERVICE_DONE: {
target: "success"
}
},
invoke: {
src: (context, data) => Promise.resolve({ prop: "first service" }),
onDone: {
// target: 'success',
actions: [
assign({ data: "hello world" }),
//instead of using target:'success' send a custom
//event that has access to the data from the service
send((context, event) => {
//event here has event.data.prop === 'first service'
console.log("send evt ", event);
return {
type: "FIRST_SERVICE_DONE",
prop: event.data.prop
};
})
]
},
onError: "failure"
}
},
success: {
invoke: {
src: (_ctx, evt) => {
console.log("evt ", evt.prop); //first service
}
}
},
failure: {}
}
});
const service = interpret(machine);
service.start();
service.send({ type: "VALIDATE" });
Codesandbox
In Xstate the context is the extended state, so it doesn't seem like a good practice to use the context as a "machine memory". The extended state is used so that you don't have a potentially infinite number of states.
In case you need to preserve information that is sent by the event going to the state that invokes a Promise, you can add that information to the response. For example:
export const myMachineServices = {
myRequest: (context, event) =>
new Promise(resolve => {
setTimeout(() => {
resolve({
payload: 'some data',
})
}, 1000)
}).then(res => ({
...res,
event.data
}))
.catch(err => ({
...err,
event.data
})),
}

PayPal React shows extra buttons after changing amount

WITHOUT react-paypal-button-v2 ~~~has an ovehead of 60KB
Similar question here but they suggest react-paypal-button-v2
I'm Trying to make a React PayPal button that changes the billing amount on props change.
I call the following component with props price and every time the price change i would like to re-render the button to update the actual price. WITHOUT react-paypal-button-v2
const PaypalForm = props => {
let paypalRef = useRef();
useEffect(() => {
window.paypal
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: "test",
amount: {
currency_code: "USD",
value: props.price
}
}
]
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
console.log(order);
},
onError: err => {
setError(err);
console.error(err);
}
})
.render(paypalRef.current);
}, [props.price]);
return (
<Row className="justify-content-center">
{error && <div>Uh oh, an error occurred! {error.message}</div>}
<div ref={paypalRef} />
</Row>
);
};
Everything is working except that a new button is created and added in the bottom of old one at each props change. I would like my new button to replace the old one. Without using react-paypal-button-v2
Something like:
useEffect(() => {
if(window.myButton) window.myButton.close();
window.myButton = window.paypal
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: "test",
amount: {
currency_code: "USD",
value: props.price
}
}
]
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
console.log(order);
},
onError: err => {
setError(err);
console.error(err);
}
});
window.myButton.render(paypalRef.current);
However, you do not actually need to re-render the button on price change!
You can do value: document.getElementById('...').value or similar (or whatever variable or function call you need)
In your example, if props.price returns the (new/current) desired value when the button is clicked, then that value will be used.
Basically, the createOrder function isn't called until you click a button.

React Native If Exist in Firebase

My firebase structure:
Hello, i am trying;
One person opening a announcement with own userid. After another person get in this announcement's inside. If second person wants to send a notification to first person, he uses first person's userid's table in firebase and he put own userid's to first persons userid's table. If first person wants to see, who sends notification to him, he looks to own user id's table and he sees other peoples userids (other people are sended notification to him).. Now one person can send lots of time notification, i want to one person can send just one time for this reason i am trying to control it.
if (firebase.database().ref(`/bavuruistek/${userid}`).child(katilan) === null ) {
firebase.database().ref(`/bavuruistek/${userid}`)
.push({
katilan, istek })
.then(() => {
dispatch({ type: STUDENT_REQUEST_SUCCESS });
Actions.pop();
});
}
if (firebase.database().ref(`/bavuruistek/${userid}`).child(katilan) !== null) {
console.log(firebase.database().ref(`/bavuruistek/${userid}/${katilan}`));
Alert.alert(
'Mesaj',
'Daha önce başvurunuz yapılmış!',
[
{ text: 'Tamam', onPress: () => null }
]
);
}
firebase.database().ref(`/basvuruistek/${userid}`).on('value', (snapshot) => {
const { currentUser } = firebase.auth();
const notes = snapshot.val();
Object.keys(notes).forEach(key => {
if (notes[key].katilan !== currentUser.uid) {
this.setState({ click: true });
} if (notes[key].katilan === currentUser.uid) {
this.setState({ click: false });
}
});
});
You are trying to use firebase.database().ref(`/bavuruistek/${userid}`).child(katilan) inside an if statement, which doesn't work because 1) .child() returns a Reference object. Trying something like this may help you achieve the intended outcome:
firebase.database().ref(`/bavuruistek/${userid}`).child(katilan).once('value').then(snapshot => {
if (snapshot.val() === null ) {
firebase.database().ref(`/bavuruistek/${userid}`)
.update({
katilan, istek
})
.then(() => {
dispatch({ type: STUDENT_REQUEST_SUCCESS });
Actions.pop();
});
} else {
console.log(snapshot.val());
Alert.alert(
'Mesaj',
'Daha önce başvurunuz yapılmış!',
[
{ text: 'Tamam', onPress: () => null }
]
);
}
}
The main difference is that I'm using .once, reading the resulting snapshot value, and using that as part of the if statement to check if it is null.

Reactjs/Apollo/AppSync Mutation Optimistic Response Resolved ID

So first off I will start by saying I added an optimistic response to my mutation so it would it stop producing duplicates as referenced here and from this previous S.O. question.
So that is all working but I have a set of dependant mutations that run after the first using async await.
submitForm = async () => {
// Only submit if form is complete
if (!this.state.saveDisabled) {
try {
// Optimistic Response is necessary because of AWS AppSync
// https://stackoverflow.com/a/48349020/2111538
const createGuestData = await this.props.createGuest({
name: this.state.name,
})
let guestId = createGuestData.data.addGuest.id
for (let person of this.state.people) {
await this.props.createPerson({
variables: {
name: person.name,
guestId,
},
optimisticResponse: {
addPerson: {
id: -1, // A temporary id. The server decides the real id.
name: person.name,
guestId,
__typename: 'Person',
},
},
})
}
this.setState({
redirect: true,
})
} catch (e) {
console.log(e)
alert('There was an error creating this guest')
}
} else {
Alert('Please fill out guest form completely.')
}
}
Now this works and it is using the same pattern for the mutation as per the sample project
export default compose(
graphql(CreateGuestMutation, {
name: 'createGuest',
options: {
refetchQueries: [{ query: AllGuest }],
},
props: props => ({
createGuest: guest => {
console.log(guest)
return props.createGuest({
variables: guest,
optimisticResponse: () => ({
addGuest: {
...guest,
id: uuid(),
persons: [],
__typename: 'Guest',
},
}),
})
},
}),
}),
graphql(CreatePersonMutation, {
name: 'createPerson',
}),
)(CreateGuest)
The only problem is that I can't force the state to get updated to the ID that actually gets inserted when using Async Await, so all the person entries get the place holder UUID. Note, I have also tried using id: -1 as is done with the createPerson mutation but that didn't change anything, it just used negative one for all the entires.
Is there a better way of doing this? I am doing something wrong. This all worked without the optimisticResponse but it always created two entries per mutation.
Can you try this again? There were enhancements to the AppSync SDK for Javascript which no longer require you to use Optimistic Response. You can use it optionally if you still want an optimistic UI.
Additionally you can also now disable offline if that's not a requirement for your app by using disableOffline like so:
const client = new AWSAppSyncClient({
url: AppSync.graphqlEndpoint,
region: AppSync.region,
auth: {
type: AUTH_TYPE.API_KEY,
apiKey: AppSync.apiKey,
},
disableOffline: true
});

Resources