How should I fetch payment intent secret for Stripe Elements in my Next.js app? - stripe-payments

I am trying to implement Stripe payments in my Next.js app as described in the guide here: https://stripe.com/docs/payments/quickstart
The guide tells me that in order to use Stripe Elements for my checkout form, I need to know payment intent. It says:
Create PaymentIntent as soon as the page loads
The issue is - our website will not have a separate payments page, the payment form will be displayed inside the modal, which is loaded on every page of the website. That means, I would have to fetch the payment intent for any user who ever visits any page on our website, whether they're planning to purchase the course or not, just so that I could display the payment form inside the modal. That doesn't seem right to me.
Can you give me some advice, let me know if there's a better way to handle this?
Another issue is that this guide tells me that I should pass the fetched payment intent clientSecret as an option to <Elements/> wrapper.
And if I hover on <Elements/> wrapper in my VSCdoe, it tells me:
[...] Render an Elements provider at the root of your React app so that it is available everywhere you need it. [...]
So, does that mean I have to put <Elements/> wrapper into my _app.tsx file? And that means I'd have to fetch the payment intent clientSecret inside of the _app.tsx? So that my app would fetch payment intent secret any time any user ever loads any page on my website?
Again, this seems pretty weird, wouldn't it slow things down, add extra requests and loading time to all my pages, and create a whole bunch of payment intents that are never used?

Render the payment form in a modal in Layout.js and wrap the
entire project in the Layout component
place this code in _app.js
import React, { useEffect, useState } from "react"
import { loadStripe } from "#stripe/stripe-js"
import { Elements } from "#stripe/react-stripe-js"
import Layout from "../components/Layout"
import PaymentModalForm from "../components/PaymentModalForm"
const promise = loadStripe("pk_test_....")
// replace pk_test_... with your publishable key
const API_URL = "http://localhost:8000"
// replace API_URL with your backend server url
const App = ({ Component, pageProps }) => {
const [secret, setSecret] = useState(null)
useEffect(() => {
const fetchSecret = async () => {
const response = await fetch(`${API_URL}/create_intent`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: [{ id: 'adidas boost', quantity: 2}]
})
})
const { client_secret } = await response.json()
setSecret(clientSecret)
}
fetchSecret()
}, [])
const options = {
clientSecret: secret,
appearance: { theme: "stripe"}
}
return (
{secret && (
<Elements stripe={promise} options={options}>
<Layout>
<Component {...pageProps} />
</Layout>
</Elements>
)}
)
}
export default App
Then in your Layout.js, fill in this code
import PaymentModalForm from "../components/PaymentModalForm"
import React, { useEffect, useState } from "react"
const Layout = ({ children }) => {
const [showModal, setShowModal] = useState(false)
const handleClick = () => {
if (showModal) {
setShowModal(false)
} else {
setShowModal(true)
}
}
return (
<div>
<div className="container">
{children}
<button onClick={handleClick}>Show Payment Modal</button>
</div>
{showModal ? (
<div className="modal fade">
<div className="modal-dialog">
<div className="modal-content">
<PaymentModalForm />
</div>
</div>
</div>
) : ( null )}
</div>
)
}
export default Layout
There's more work to be done in PaymentModalForm.js

Related

Need to call an api for each key stroke in react, but the response can have thousands of objects

I am using react and axios for frontend, and nextjs with prisma for backend. I have in the database 4000 exercices that contain fitness exercices. I want to create a function where by each key stroke, the api will look for the relevant exercice. I finished creating it, but i have some issues:
The main problem is that the response is delayed from the first keystrokes, because the payload response is tooo large. I created a scrollable UL element to render the elements, because I want to get also the Gif images. So the elements, if the API will find those, will be rendered on the screen.
If I add to each element an on click event, to select the exercice's Id, I get an error "too many re-rendering on the screen".
How can I optimise the function, and how can I solve the error of too many re-render on the screen? Nextjs tells me that it will create an infinite loop....
The frontend looks like this:
const [open, setOpen] = useState(false);
const [keyWord, setKeyWord] = useState('');
const [array, setArray] = useState([]);
const [exerciceId, setExerciceId] = useState('');
// Add exercice
const hadnleAddExercie = async event => {
event.preventDefault();
console.log('exercice added');
}
// Look for exercices
const searchExercices = async event => {
event.preventDefault();
setKeyWord(event.target.value);
const arrayExercices = await getExercicesByKeyWords(keyWord);
setArray(arrayExercices);
console.log(arrayExercices);
}
<div className='flex mt-3 flex-col'>
<input onChange={searchExercices} required placeholder='Search by word...' className='border border-slate-400 p-1 rounded-md flex-1 max-w-sm my-2'/>
<ul className='border border-slate-400 p-1 rounded-md max-w-sm my-2 max-h-52 overflow-scroll'>
{
array.length > 1 && array.map(exercice => (
<li key={exercice.id} className='flex flex-wrap p-2 bg-slate-200 m-2 items-center rounded-md'>
<span><Image className='rounded-xl mr-2' priority width={40} height={40} src={exercice.gifUrl} alt={exercice.name}/></span>
<span>{ exercice.name }</span>
</li>
))
}
</ul>
</div>
The backend Uses prisma and I use the OR clause to look for a word in different rows:
export default async function handler(req, res) {
try {
const param = req.query.slug[0];
console.log(param);
// Get exercices where the two rows contains a single parametter
const exercices = await prisma.exercices.findMany({
where: {
OR: [
{
name: {
contains: param
}
},
{
target: {
contains: param
}
},
{
equipment: {
contains: param
}
}
]
}
});
res.status(200).send(exercices);
}
catch (error) {
console.log(error);
res.status(500).send(error);
}
}
An example can be this:
Only for finding an exercice I used 500mb...
Here are a few ways I can think of to optimize this:
Use pagination and fetch more results as user scrolls down or actually separate it by using pages. You can read more on how to implement pagination in Prisma here.
Add debounce to your search term so it doesn't actually fire on every single keystroke, you could use something like useDebounce.
Use React.memo to prevent the list from being re-rendered every time some state changes, only re-render it when the actual list changes.

Load WooCommerce data on demand and show it in a DataGrid by Syncfusion

I need to load all the products in my nodeJS application with WooCommerce Rest Api. I use the WooCommerce REST API - JavaScript Library and the Syncfusion Grid Component. Because I can't load all data at once, I wanted to use the Load data on demand like this, but I can't find any documentation or examples on this.
I have something like this:
import React from 'react';
import { useEffect, useState } from "react";
import { GridComponent, ColumnsDirective, ColumnDirective, Resize, Sort, ContextMenu, Filter, Page, ExcelExport, PdfExport, Edit, Inject } from '#syncfusion/ej2-react-grids';
import WooCommerceRestApi from "#woocommerce/woocommerce-rest-api";
var WooCommerce = new WooCommerceAPI({
url: 'http://example.com',
consumerKey: 'ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
consumerSecret: 'cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
wpAPI: true,
version: 'wc/v1'
});
const WooCommerceProducts = () => {
const [products, setProducts] = useState([]);
useEffect(() => {
fetchOrders();
}, []);
let fetchOrders = () => {
WooCommerce
.get("products", {
per_page: 100,
page: 1
})
.then((response) => {
if (response.status === 200) {
setProducts(response.data);
}
})
.catch((error) => { });
};
return (
<div className='m-2 md:m-10 p-2 md:p-10 bg-white rounded-3xl'>
<Header category="Page" title="WooCommerce Orders" />
<GridComponent
id='gridcomp'
dataSource={orders}
allowPaging
allowSorting>
<ColumnsDirective>
<ColumnDirective field='id' />
<ColumnDirective field='name' />
<ColumnDirective field='slug' />
<ColumnDirective field='status' />
...
</ColumnsDirective>
<Inject services={[Resize, Sort, ContextMenu, Filter, Page, ExcelExport, PdfExport]} />
</GridComponent>
</div>
)
}
export default WooCommerceProducts
Please help and thx
If you are using any custom services, I suggest you use the custom-binding feature to bind the data to the grid. I would like to share the behavior of custom-binding in EJ2 Grid. 
For every grid action (such as Filter, Page, etc.,), I have triggered the dataStateChange event, and, in the event arguments, I have sent the corresponding action details (like skip, take, filter field, value, sort direction, etc.,) Based on that, you can perform the action in your service, return the data as a result, and count objects. 
Note: ‘dataStateChange’ event is not triggered at the Grid initial render. If you are using a remote service, you need to call your remote service by manually with a pagination query (need to set the skip value as 0 and take a value based on your pageSize of pageSettings in Grid. If you are not defined pageSize in pageSettings, you need to send the default value 12 ) in load event of Grid. Please return the result like as "{result: […], count: …}" format to Grid. 
‘dataSourceChanged’ event is triggered when performing CRUD actions in Grid. You can perform the CRUD action in your service using action details from this event, and, you need to call the endEdit method to indicate the completion of the save operation. 
Custom-binding: https://ej2.syncfusion.com/react/documentation/grid/data-binding/data-binding/#custom-binding
Demo: https://ej2.syncfusion.com/react/demos/#/material/grid/custom-binding
sample: https://stackblitz.com/edit/react-v64sms-wx3hsy?file=index.js

React hook for Facebook Comment - preload

How to pre-load an iframe with React hook.
Recently i implemented a Facebook Comment Tool on my page. https://jackylenghia.com/#/Contact
However, look like iframe requires a preload (i.e. in ComponentDidMount in old React), so the iframe does not appear, but you will need to refresh the page (F5 or Cmd + R), to let the iframe loaded.
How should I fix this with React Hook.
I tried to by loading this with useEffect but does not help
Original:
const Contact = (props) => {
return(
... //Other stuffs
<Col>
<div class="fb-comments" data-href="https://jackylenghia.com/#/Contact" data-numposts="5" data-width=""></div>
</Col>
...
)
}
What I tried
const Contact = (props) => {
const [fbcomment, setfbcomment] = useState(0);
useEffect(() => {
setfbcomment(`<div class="fb-comments" data-href="https://jackylenghia.com/#/Contact" data-numposts="5" data-width=""></div>`);
});
return(
... //Other stuffs
<Col>
{fbcomment}
</Col>
...
)
}
But this only print out a text string only, instead of inserting the element to dom
Turn out, to preload the iframe, I only need to run this from FB JavaScript SDK, in useEffect:
useEffect (() => {
FB.XFBML.parse();
});
This will request FB component to be parsed after dom loaded.

Nextjs how to not unmount previous page when going to next page (to keep state)

we are using Nextjs in our web app.
We want to keep stack of pages where users visit to keep state of component on back navigation.
How should we do that?
I have tried https://github.com/exogen/next-modal-pages, but it calls getInitialProps of previous pages again on back.
Here's my solution with a custom _app.js
import React, { useRef, useEffect, memo } from 'react'
import { useRouter } from 'next/router'
const ROUTES_TO_RETAIN = ['/dashboard', '/top', '/recent', 'my-posts']
const App = ({ Component, pageProps }) => {
const router = useRouter()
const retainedComponents = useRef({})
const isRetainableRoute = ROUTES_TO_RETAIN.includes(router.asPath)
// Add Component to retainedComponents if we haven't got it already
if (isRetainableRoute && !retainedComponents.current[router.asPath]) {
const MemoComponent = memo(Component)
retainedComponents.current[router.asPath] = {
component: <MemoComponent {...pageProps} />,
scrollPos: 0
}
}
// Save the scroll position of current page before leaving
const handleRouteChangeStart = url => {
if (isRetainableRoute) {
retainedComponents.current[router.asPath].scrollPos = window.scrollY
}
}
// Save scroll position - requires an up-to-date router.asPath
useEffect(() => {
router.events.on('routeChangeStart', handleRouteChangeStart)
return () => {
router.events.off('routeChangeStart', handleRouteChangeStart)
}
}, [router.asPath])
// Scroll to the saved position when we load a retained component
useEffect(() => {
if (isRetainableRoute) {
window.scrollTo(0, retainedComponents.current[router.asPath].scrollPos)
}
}, [Component, pageProps])
return (
<div>
<div style={{ display: isRetainableRoute ? 'block' : 'none' }}>
{Object.entries(retainedComponents.current).map(([path, c]) => (
<div
key={path}
style={{ display: router.asPath === path ? 'block' : 'none' }}
>
{c.component}
</div>
))}
</div>
{!isRetainableRoute && <Component {...pageProps} />}
</div>
)
}
export default App
Gist - https://gist.github.com/GusRuss89/df05ea25310043fc38a5e2ba3cb0c016
You can't "save the state of the page by not un-mounting it" but you can save the state of your app in _app.js file, and the rebuild the previous page from it.
Check the redux example from next's repo.

actions/reducers are not causing a rerender as expected

I am building a web client (react,redux) & API (mongo, express, node) that will show a list of deals to a user and allow them to "favorite/like" them. I am new to react/redux, as you will be able to tell. I am using axios to make my requests and have successfully rendered a list of deals. I have a "favorite" button that successfully makes the post request, and the request just sends back the deal that was favorited.. However, the "number of likes" is not updating and does not show the increased number until I manually refresh the page.
Here is my component that successfully produces a list of deals (2)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchDeals, favoriteDeal } from '../actions';
import DealCard from './DealCard';
class DealList extends Component {
componentDidMount(){
this.props.fetchDeals();
this.favoriteDeal = this.favoriteDeal.bind(this);
}
favoriteDeal = (dealId) => {
this.props.favoriteDeal(dealId)
}
renderDeals(){
return this.props.deals.map(deal => {
return(
<DealCard
onFavorite = {this.favoriteDeal}
key={deal._id}
{...deal}
/>
)
});
}
render(){
return(
<div>
{this.renderDeals()}
</div>
);
}
}
function mapStateToProps(state){
return {
deals: state.deals,
favoriteDeal: state.favoritedDeal
}
}
export default connect(mapStateToProps, {fetchDeals, favoriteDeal})(DealList)
Below is my individual deal card:
import React, { Component } from 'react';
class DealCard extends Component {
render() {
return (
<div key={this.props._id} className="card" style={{width: "18rem", marginTop: 10}}>
<img className="card-img-top" src={this.props.dealImage} style={{maxHeight: 200}} alt="${this.props.dealHeadline}" />
<div className="card-body">
<h4>{this.props.dealHeadline}</h4>
<p className="card-text">{this.props.dealDescription}</p>
<div>
<button onClick={() => this.props.onFavorite(this.props._id)}>Favorite</button>
<span>{this.props.dealId}</span>
<i className="fa fa-heart" aria-hidden="true"></i>
<p className="card-text">#of Likes: {this.props.dealNumberOfLikes}</p>
</div>
</div>
</div>
);
}
}
export default DealCard;
Below are my action creators:
export const fetchDeals = () => async dispatch => {
const res = await axios.get('/api/deals')
dispatch({type: FETCH_DEALS, payload: res.data})
};
export const favoriteDeal = (dealId) => async dispatch => {
const res = await axios.post(`/api/deals/${dealId}/favorites`)
dispatch({type: FAVORITE_DEAL, payload: res.data})
};
and finally my reducers:
// deals reducer
import { FETCH_DEALS } from '../actions/types';
export default function (state = [], action){
switch(action.type){
case FETCH_DEALS:
return action.payload;
default:
return state;
}
};
// favorite deals Reducer
import { FAVORITE_DEAL } from '../actions/types';
export default function (state = {}, action){
switch(action.type){
case FAVORITE_DEAL:
return action.payload;
default:
return state;
}
};
To summarize: I have a list of deals, and each deal has a button that when clicked, "favorites" a deal via an HTTP post request and increases the NumberOfDealLikes by 1. When the button is clicked, the request is successfully executed and the database shows that the NumberOfDealLikes is increased by one. However, on the screen, the update is not shown until I manually rerender. As twitter works, I would like to show that the increase happens simultaneously.
Thank you all for your help!
I think the problems lies in your favorite_deal reducer. As you said, the post request sends back the updated deal. It should then replace the old one in the deals array. Your deals reducer should look like:
import { FETCH_DEALS, FAVORITE_DEAL } from '../actions/types';
export default function (state = [], action){
switch(action.type){
case FETCH_DEALS:
return action.payload;
case FAVORITE_DEAL:
return state.map((d) => d._id === action.payload._id ? action.payload : d);
default:
return state;
}
};
As the deals array is updated, your component will be re-rendered. And you do not need another reducer.
By the way, as you defined the favoriteDeal function as a class property with an arrow function, you do not need to bind it to this.

Resources