#KoalaKey - useEffect
1 messages ยท Page 1 of 1 (latest)
Hello! Unfortunately I'm not very familiar with React, but I'll do what I can to help! Is this code from a guide or tutorial?
It's just a practice file I came up with. Essentially I am just playing around with this because I would eventually like to render some content on a page based on the results from a larger query
Oh, I think I see the issue.
Try putting async before the function and then add await before the stripe.customers.search call.
Oh, no... I guess that's not really it... you're using .then on customer...
That's an unusual way to go about things. The customer isn't really the Customer, it's a Promise.
Yeah i had tried that haha. Yeah, i could've named it better but like I said it's just some practice I'm currently doing
Usually Stripe.js would not be initialized inside useEffect. Typically you would do something like this instead:
import React, {useState, useEffect} from 'react';
import {useStripe} from '@stripe/react-stripe-js';
const PaymentStatus = () => {
const stripe = useStripe();
const [message, setMessage] = useState(null);
useEffect(() => {
if (!stripe) {
return;
}
// Other code...
That's from one of the snippets here: https://stripe.com/docs/payments/accept-a-payment?platform=web&ui=elements&html-or-react=react
Oh wow, okay I'll check that out! thanks for this!
And then earlier you would have done const stripePromise = loadStripe('pk_test_...'); somewhere.
(NOT with Stripe) I am pretty familiar with React, if you want a 2nd set of eyes
Yeah the more the merrier! Been toying with this for a couple days haha
Very Basic Point First: useEffect MUST BE SYNCHRONOUS
@dense nymph Thanks for jumping in to help! Also, as an aside, it's okay to not prefix your messages with "(NOT with Stripe)"; we have the Stripe Staff role and badges to indicate who on the server is from Stripe. ๐
That's not for your benefit; it's for everyone else's so they don't put undue faith in me
So: you can fire off a closure to run an asynchronous function, but you really need to be careful. But by definition and design, useEffect cannot be asynchronous
That's fair, and if you'd like to keep including a disclaimer that's fine, but might I suggest moving it to the end of your messages? When I first saw you use it I thought you were indicating you couldn't do something on Stripe. ๐
I'll keep it in mind
I will now be quiet so you two can sort out the React issue. ๐
so: stripe needs to be initiated, but not with useEffect. I can show you my paymentEmbed code, if you like - I expect I'll like publish some of this stuff, maybe as libraries, at some point soon
Step one: it is recommended to initialize Stripe at a fairly HIGH level in your code - it gives Radar and other Fraud systems far more signals to work with
yeah that would be great if you could. I tried initializing stripe outside of the useEffect block initially but I was still running into the same problem of nothing being logged to the console. the initialization within the useEffect was just my latest code
so I have this loaded as part of quite a library of code:
import { loadStripe } from "@stripe/stripe-js";
export let stripe = null;
export let stripe_started = false;
export let stripe_running = false;
(async () => {
try {
stripe = await loadStripe("pk_test_xxxx");
stripe_running = true;
} catch (err) {
stripe_running = false;
} finally {
stripe_started = true;
}
})();
Note the closure, as there is no top-level await in a browser
oh I see what you are doing now
the impoprt of this starts in top-level app.js
THEN
(couple mo's for formats, etc)
(too many tabs open)
haha I feel that. I always have a ton open
Raect Component Outer:
import React, { useEffect, useState } from "react";
import { SpinnerDiamond } from "spinners-react";
import { Elements } from "@stripe/react-stripe-js";
import PaymentInput from "../PaymentInput";
import Popup from "reactjs-popup";
import {
capturePaymentMethod,
stripe,
PAYMENT_THEME,
fixPledgeTicket,
logger
} from "../../services";
export default function PaymentPopupButton(props) {
const { fan, status } = props;
const [open, setOpen] = useState(false);
const closeModal = () => setOpen(false);
useEffect(() => {
closeModal();
}, [fan]);
return (
<>
<div
type="cell button expanded rounded"
className={`cell button expanded rounded ${status}`}
onClick={() => setOpen((o) => !o)}
>
{`Payment Method: ${
fan?.payment_method ? fan.payment_method : "Create One Now"
}`}
</div>
<Popup
open={open}
onClose={closeModal}
modal
nested={true}
closeOnDocumentClick
>
<PaymentEmbed fan={fan} />
</Popup>
</>
);
}
export const PaymentEmbed = (props) => {
const { fan } = props;
const [options, setOptions] = useState(null);
const [secret, setSecret] = useState(null);
useEffect(() => {
if (!secret) {
(async () => {
//this process INITIATES generation of a payment_secret
const result = await capturePaymentMethod({ personId: fan.Id });
result?.data && setSecret(result.data.payment_secret);
})();
} else {
setOptions({
// passing the client secret obtained in step 2
clientSecret: secret,
// Fully customizable with appearance API.
appearance: {
theme: PAYMENT_THEME
/*...*/
}
});
}
}, [fan, secret]);
const doConfirm = async (...args) => {
return stripe.confirmSetup(...args);
};
return options ? (
<Elements stripe={stripe} options={options}>
<PaymentInput confirming={doConfirm} />
</Elements>
) : (
<SpinnerDiamond />
);
};
There's a few application specific bits there, but I don't think they'll distract
No no this is good! I really appreciate it!
The PaymentEmbed is the more relevant part
the createPaymentMethod is a remote server call - I use Firebase Cloud Function Triggers
Wait so am I even loading in stripe the correct way if I want to call the apis?
because it looks like you are using the stripe js sdk
which I do not think has the ability to call the customer apis?
at least I never saw a way in the docs to do so
Well, it does, but not using it directly here - you'll see more in the PaymentInput component
The docs can be confusing... there are sections for the Server level APIs, and separately for the client APIs
...and frustratingly, almost no notations that significant sections are considered deprecated - I've put in a few feature requests to change that
so let's share the PaymentInput component, as see if that helps
Typically Stripe.js is used for only a small subset of operations, with most operations requiring your secret key and server-side code.
(which is how I'm structured)
Customer operations are typically server-side, for example.
ahh I see. So essentially what you are saying is that I should not even be querying for the customer metadata where I am?
As a rule, if it requires a secret, I only have it on Server side. I've built up a library of server-side functions
ahhh gotcha, I'll have to look into doing that
ask what you need; I'll wait before driving convo offscreen with next code section
Nahh I'm good you can share it
PaymentInput component:
import React, { useState } from "react";
import { SpinnerDiamond } from "spinners-react";
import {
useStripe,
useElements,
PaymentElement
} from "@stripe/react-stripe-js";
import { COMPANY_NAME, ERROR_TYPES, logger } from "../../services";
export default function PaymentInput(props) {
const { confirming } = props;
const stripe = useStripe();
const elements = useElements();
const [error_message, set_error_message] = useState(null);
const [showSpinner, setShowSpinner] = useState(true);
const handleSubmit = async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
event && event.preventDefault();
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
setShowSpinner(true);
const result = await confirming({
//`Elements` instance that was used to create the Payment Element
elements,
confirmParams: {
return_url: window.location.href
},
redirect: `if_required`
});
if (result?.error) {
switch (result.error.type) {
case ERROR_TYPES.CARD_ERROR:
case ERROR_TYPES.VALIDATION_ERROR:
set_error_message(
`${
result.error.message_code
? "Code: " + result.error.message_code + " : "
: ""
}${result.error.message} Try re-submitting`
);
break;
default:
break;
}
// Show error to your customer (e.g., payment details incomplete)
logger(result.error.message);
setShowSpinner(false);
}
/**
* if there is no error, the user record ("People") changes state,
* and this entire component is removed.
*/
};
const handleReady = async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
setShowSpinner(false);
};
return (
<form onSubmit={handleSubmit}>
{showSpinner && <SpinnerDiamond />}
<PaymentElement
onReady={handleReady}
options={{ business: { name: COMPANY_NAME } }}
/>
<div>{error_message ? error_message : null}</div>
<button
className="button"
type="submit"
disabled={!stripe || showSpinner}
>
Submit
</button>
</form>
);
}
There's a bit of my general philosophy and architecture hidden in here, although noted.
My general flow is FrontEnd => stripe => webhook => database => database listener => front end
So you may not see a direct "return" for some of my structure
No this is good! thank you!
In this case, if the setupIntent succeeds, the paymentMethod is noted on a "Person" database record - which updates a "listener" which changes an object in Redux
If the confirm has errors (or the form has errors), no Redux state change occurs and the errors are displayed
OUTSIDE of the code shown, I first check if the Person record has an existing paymentMethod, if so, this component never shows.
(to confuse you more - it's tristate - if they're not logged in it pops up a login modal; if the logged in user doesn't have a paymentMethod it pops up the capture modal, else it becomes the "pay" button)
That's what I would eventually like to do. Check to see if a customer has an active subscription and if they do I want to be able to hide a button on a page. But, I thought the way to do it was to query the API right in the useEffect and set a state.
Wow that must be quite the application haha
Redux, used for what it's meant for, is useful here. Global State - such as the status of the logged in user. Then it's just a display decision in your components
It's a couple hundred thousand lines of application code, a similar number of Cloud Function/Server code, and a similar number of lines of an npm "wrapper" library I built for firebase
the last is to share a LOT of common code between frontend and backend
wow impressive! if you do release the files as a library I'd love to know
Does the above help clarify some of the flow involved?
yeah definitely I appreciate the help! I think I am going to rethink how I go about achieving this
I assume you were "just" trying to test a query to stripe?
yes
Gets trickier, because the calls you are using are built for server-side. BUT I believe there are calls to use a "publishable" and/or "ephemeral" key on the front end for limited access. Probably want to look at this documentation: https://stripe.com/docs/js
...which is browser-side, as opposed to this https://stripe.com/docs/api which is server-side
Complete reference documentation for the Stripe API. Includes code snippets and examples for our Python, Java, PHP, Node.js, Go, Ruby, and .NET libraries.
Well could I just make my 'query' an api route in my application and then fetch the result and pass it to the front end? that seems like it would make more sense in my case
Also, if you haven't done so, really read the developer documentation. I find a LOT of people try to start directly in the API docs, which is gonna lead you down some dark places, especially as the deprecated bits aren't well marked
Yes, that is exactly what is intended by stripe
Build yourself your own API for your needs to isolate Stripe mostly to the safe server side
My own system is a React front end, Google Firebase Cloud Functions as a backend tied together with Firebase Firestore as my DB
okay that makes much more sense. I haven't really had the need to access the API's as of yet since I am using Auth0 for the user data store and all of my needs up until now I have been able to take care of using Auth0 actions. I've been reading the docs but its definitely a struggle sometimes haha. My application is React front end, Auth0 for security and user store, and Airtable for the datastore
Oddly, I started with Airtable, but blew out of it's abilities almost immediately. The biggest reason for my wrapper library was to hide the difference between Firestore and Airtable.
My system is a tad more complex than a subscription
oh wow, yeah I'm not doing anything super crazy like you are haha. Just giving users access to data in Airtable
Start with the browser-side API doc, and the developer docs. between them they're a pretty good tutorial. The server-side API docs are a LOT more dry
Oh, and probably need to read the Reactjs documentation too, especially the hooks. They have VERY specific requirements.
Yeah I'll definitely be going back and digging through the docs for sure
I appreciate all the help!
I'm off; good luck.