I'm trying to implement the Adyen dropin payment UI using NextJS but I'm having trouble initializing the Adyen dropin component.
I'm need to dynamically import Adyen web or I get the error window is not defined however, after reading through the NextJS docs, dynamic import creates a component which I can't figure out how to use as a constructor.
I tried the code below but receive the error TypeError: AdyenCheckout is not a constructor
I'm new to NextJS and am at a total loss as to how I should import and initialize Adyen.
Can anyone point me in the right direction?
import Head from 'next/head';
import { useRef, useEffect, useState } from 'react';
import {callServer, handleSubmission} from '../util/serverHelpers';
//dynamic import below. Imports as a component
//import dynamic from 'next/dynamic';
//const AdyenCheckout = dynamic(() => import('#adyen/adyen-web'), {ssr: false});
import '#adyen/adyen-web/dist/adyen.css';
export default function Dropin(){
const dropinContainer = useRef(null);
const [paymentMethods, setPaymentMethods] = useState();
//const [dropinHolder, setDropinHolder] = useState();
//Get payment methods after page render
useEffect( async () => {
const response = await callServer(`${process.env.BASE_URL}/api/getPaymentMethods`);
setPaymentMethods(prev => prev = response);
},[]);
//Adyen config object to be passed to AdyenCheckout
const configuration = {
paymentMethodsResponse: paymentMethods,
clientKey: process.env.CLIENT_KEY,
locale: "en_AU",
environment: "test",
paymentMethodsConfiguration: {
card: {
showPayButton: true,
hasHolderName: true,
holderNameRequired: true,
name: "Credit or debit card",
amount: {
value: 2000,
currency: "AUD"
}
}
},
onSubmit: (state, component) => {
if (state.isValid) {
handleSubmission(state, component, "/api/initiatePayment");
}
},
onAdditionalDetails: (state, component) => {
handleSubmission(state, component, "/api/submitAdditionalDetails");
},
};
//const checkout = new AdyenCheckout(configuration);
const AdyenCheckout = import('#adyen/adyen-web').default;
const adyenCheckout = new AdyenCheckout(configuration);
const dropin = adyenCheckout.create('dropin').mount(dropinContainer.current);
return (
<div>
<Head>
<title>Dropin</title>
</Head>
<div ref={dropin}></div>
</div>
)
}
I was able to resolve the issue by importing the module using the default value inside an async function nested in the useEffect function.
import Head from 'next/head';
import { useRef, useEffect, useState } from 'react';
import {callServer, handleSubmission} from '../util/serverHelpers';
import '#adyen/adyen-web/dist/adyen.css';
export default function Dropin(){
const dropinContainer = useRef();
const [paymentMethods, setPaymentMethods] = useState({});
useEffect(() => {
const init = async () => {
const response = await callServer(`${process.env.BASE_URL}/api/getPaymentMethods`)
.then(setPaymentMethods(response));
console.log(paymentMethods);
const configuration = {
paymentMethodsResponse: paymentMethods,
clientKey: process.env.CLIENT_KEY,
locale: "en_AU",
environment: "test",
paymentMethodsConfiguration: {
card: {
showPayButton: true,
hasHolderName: true,
holderNameRequired: true,
name: "Credit or debit card",
amount: {
value: 2000,
currency: "AUD"
}
}
},
onSubmit: (state, component) => {
if (state.isValid) {
handleSubmission(state, component, "/api/initiatePayment");
}
},
onAdditionalDetails: (state, component) => {
handleSubmission(state, component, "/api/submitAdditionalDetails");
},
};
console.log(configuration.paymentMethodsResponse);
const AdyenCheckout = (await import('#adyen/adyen-web')).default;
const checkout = new AdyenCheckout(configuration);
checkout.create('dropin').mount(dropinContainer.current);
}
init();
},[]);
return (
<div>
<Head>
<title>Dropin</title>
</Head>
<div ref={dropinContainer}></div>
</div>
)
}
Related
I'm fairly new to React testing library and am using a function within a useEffect to decode a user token from keycloak when they sign up, to determine what type of user they are and render different Menu's based on that. This new Jwt function I created though, is causing a lot of my previously working tests within other files to fail as it throws an error like below:
I'm sure how to deal with this error on the testing side, should I be mocking this decoder function within the test file?
This is my main file:
import React, {useEffect, useState, useMemo, useCallback} from "react";
import {withTranslation} from "react-i18next";
import "./index.scss";
import {historyObject} from "historyObject";
import {Heading} from "#xriba/ui";
import { keycloak } from "utils/keycloak";
export const Menu = (props) => {
const {t} = props;
const [userHasCommunities, setUserHasCommunities] = useState(false);
useEffect(() => {
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
if (JSON.parse(jsonPayload).communities?.length > 0){
setUserHasCommunities(true)
}
return JSON.parse(jsonPayload);
};
parseJwt(keycloak.token);
}, [userHasCommunities])
const buildItem = useCallback((item) => {
const isCurrent = historyObject.location.pathname.includes(item.path);
return <li key={item.path} className={isCurrent ? "selected" : ""} onClick={() => historyObject.push(item.path)}>
<Heading level={"4"}>{t(item.title)}</Heading>
</li>
}, [t])
const renderMenu = useMemo(() => {
let menuItems = []
if (userHasCommunities){
menuItems = [
{
path: "/portfolio",
title: "pages.portfolio.menu_entry"
},
{
path: "/transactions",
title: "pages.transactions.menu_entry"
},
{
path: "/community",
title: "pages.community.menu_entry"
},
{
path: "/reports",
title: "pages.reports.menu_entry"
}
]
} else {
menuItems = [
{
path: "/portfolio",
title: "pages.portfolio.menu_entry"
},
{
path: "/transactions",
title: "pages.transactions.menu_entry"
},
{
path: "/reports",
title: "pages.reports.menu_entry"
}
]
}
return menuItems.map(i => buildItem(i))
}, [userHasCommunities, buildItem])
return <div className={"menu"}>
<ul>
{renderMenu}
</ul>
</div>
}
export default withTranslation()(Menu)
And my current test file:
import React from "react";
import {shallow} from "enzyme";
import {Menu} from "features/layouts/logged-in/menu/index";
import {historyObject} from "historyObject";
import { keycloak } from "utils/keycloak";
jest.mock("utils/keycloak");
jest.mock("historyObject")
describe("Menu test", () => {
const props = {
t: jest.fn(),
};
beforeEach(() => {
jest.resetAllMocks();
jest.spyOn(React, "useEffect").mockImplementationOnce(x => x()).mockImplementationOnce(x => x()).mockImplementationOnce(x => x()).mockImplementationOnce(x => x());
});
it('should render Menu', () => {
const wrapper = shallow(<Menu {...props} />);
expect(wrapper).toBeDefined();
wrapper.find('li').at(0).prop('onClick')();
expect(historyObject.push).toHaveBeenCalled()
});
it('should render Menu with an object as current', () => {
historyObject.location.pathname = "/portfolio"
const wrapper = shallow(<Menu {...props} />);
expect(wrapper).toBeDefined();
expect(wrapper.find('.selected')).toHaveLength(1);
});
});
Thanks in advance for any advice!
I need to load a script on specific component in my React app.
Below is my script and i need this to load on bottom-most div in my component
<div id="rexxxx"></div>
<script>
new carouselInlineWidget("xx", {
/*Your REVIEWS.io account ID:*/
store: "xxxxxxxxxxxx",
sku: "",
lang: "en",
carousel_type: "xxxxx",
styles_carousel: "CarouselWidget--xxxxxx",
/*Widget settings:*/
options: {
general: {
/*What reviews should the widget display? Available options: company, product, third_party. You can choose one type or multiple separated by comma.*/
enable_auto_scroll: 10000,
},
header: {
},
reviews: {
},
popups: {},
},
styles: {
},
});
</script>
I have my React component
import React from 'react'
import edenredLogo from '../../images/edenred-logo.webp'
import { useHistory } from 'react-router-dom'
import { useSelector } from 'react-redux'
import './landing.less'
const Landing = () => {
const history = useHistory()
return (
<>
<div className="script-here"/>
</>
)
}
export default Landing
You can use the custom hook:
import { useEffect } from 'react';
const useScript = (url, position, async = true) => {
useEffect(() => {
const placement = document.querySelector(position);
const script = document.createElement('script');
script.src = url;
script.async = typeof async === 'undefined' ? true : async;
placement.appendChild(script);
return () => {
placement.removeChild(script);
};
}, [url]);
};
export default useScript;
Usage:
useScript(url, ".script-here");
Or just use dangerouslySetInnerHTML
<div className="script-here" dangerouslySetInnerHTML={{__html: your_script}} />
I'm quite new to React Hooks/Context so I'd appreciate some help. Please don' t jump on me with your sharp teeth. I Checked other solutions and some ways i've done this before but can't seem to get it here with the 'pick from the list' way.
SUMMARY
I need to get the municipios list of names inside of my const 'allMunicipios'(array of objects) inside of my Search.js and then display a card with some data from the chosen municipio.
TASK
Get the data from eltiempo-net REST API.
Use Combobox async element from Elastic UI to choose from list of municipios.
Display Card (from elastic UI too) with some info of chosen municipio.
It has to be done with function components / hooks. No classes.
I'd please appreciate any help.
WHAT I'VE DONE
I've created my reducer, context and types files in a context folder to fecth all data with those and then access data from the component.
I've created my Search.js file. Then imported Search.js in App.js.
I've accesed the REST API and now have it in my Search.js
PROBLEM
Somehow I'm not beeing able to iterate through the data i got.
Basically i need to push the municipios.NOMBRE from api to the array const allMunicipios in my search.js component. But when i console log it it gives me undefined. Can;t figure out why.
I'll share down here the relevant code/components. Thanks a lot for whoever takes the time.
municipiosReducer.js
import {
SEARCH_MUNICIPIOS,
CLEAR_MUNICIPIOS,
GET_MUNICIPIO,
GET_WEATHER,
} from "./types";
export default (state, action) => {
switch (action.type) {
case SEARCH_MUNICIPIOS:
return {
...state,
municipios: action.payload,
};
case GET_MUNICIPIO:
return {
...state,
municipio: action.payload,
};
case CLEAR_MUNICIPIOS:
return {
...state,
municipios: [],
};
case GET_WEATHER: {
return {
...state,
weather: action.payload,
};
}
default:
return state;
}
};
municipiosContext.js
import { createContext } from "react";
const municipiosContext = createContext();
export default municipiosContext;
MunicipiosState.js
import React, { createContext, useReducer, Component } from "react";
import axios from "axios";
import MunicipiosContext from "./municipiosContext";
import MunicipiosReducer from "./municipiosReducer";
import {
SEARCH_MUNICIPIOS,
CLEAR_MUNICIPIOS,
GET_MUNICIPIO,
GET_WEATHER,
} from "./types";
const MunicipiosState = (props) => {
const initialState = {
municipios: [],
municipio: {},
};
const [state, dispatch] = useReducer(MunicipiosReducer, initialState);
//Search municipios
//In arrow functions 'async' goes before the parameter.
const searchMunicipios = async () => {
const res = await axios.get(
`https://www.el-tiempo.net/api/json/v2/provincias/08/municipios`
// 08 means barcelona province. This should give me the list of all its municipios
);
dispatch({
type: SEARCH_MUNICIPIOS,
payload: res.data.municipios,
});
};
//Get Municipio
const getMunicipio = async (municipio) => {
const res = await axios.get(
`https://www.el-tiempo.net/api/json/v2/provincias/08/municipios/${municipio.CODIGOINE}`
//CODIGOINE is in this REST API kind of the ID for each municipio.
//I intent to use this later to get the weather conditions from each municipio.
);
dispatch({ type: GET_MUNICIPIO, payload: res.municipio });
};
const dataMunicipiosArray = [searchMunicipios];
//Clear Municipios
const clearMunicipios = () => {
dispatch({ type: CLEAR_MUNICIPIOS });
};
return (
<MunicipiosContext.Provider
value={{
municipios: state.municipios,
municipio: state.municipio,
searchMunicipios,
getMunicipio,
clearMunicipios,
dataMunicipiosArray,
}}
>
{props.children}
</MunicipiosContext.Provider>
);
};
export default MunicipiosState;
Search.js
import "#elastic/eui/dist/eui_theme_light.css";
import "#babel/polyfill";
import MunicipiosContext from "../contexts/municipiosContext";
import MunicipiosState from "../contexts/MunicipiosState";
import { EuiComboBox, EuiText } from "#elastic/eui";
import React, { useState, useEffect, useCallback, useContext } from "react";
const Search = () => {
const municipiosContext = useContext(MunicipiosContext);
const { searchMunicipios, municipios } = MunicipiosState;
useEffect(() => {
return municipiosContext.searchMunicipios();
}, []);
const municipiosFromContext = municipiosContext.municipios;
const bringOneMunicipio = municipiosContext.municipios[0];
let municipiosNames = municipiosFromContext.map((municipio) => {
return { label: `${municipio.NOMBRE}` };
});
console.log(`municipiosFromContext`, municipiosFromContext);
console.log(`const bringOneMunicipio:`, bringOneMunicipio);
console.log(`municipiosNames:`, municipiosNames);
const allMunicipios = [
{ label: "santcugat" },
{ label: "BARCELONETA" },
{ label: "BARCE" },
];
const [selectedOptions, setSelected] = useState([]);
const [isLoading, setLoading] = useState(false);
const [options, setOptions] = useState([]);
let searchTimeout;
const onChange = (selectedOptions) => {
setSelected(selectedOptions);
};
// combo-box
const onSearchChange = useCallback((searchValue) => {
setLoading(true);
setOptions([]);
clearTimeout(searchTimeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
searchTimeout = setTimeout(() => {
// Simulate a remotely-executed search.
setLoading(false);
setOptions(
municipiosNames.filter((option) =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
)
);
}, 1200);
}, []);
useEffect(() => {
// Simulate initial load.
onSearchChange("");
}, [onSearchChange]);
return (
<div>
<EuiComboBox
placeholder="Search asynchronously"
async
options={options}
selectedOptions={selectedOptions}
isLoading={isLoading}
onChange={onChange}
onSearchChange={onSearchChange}
/>
<button>Lista de municipios</button>
</div>
);
};
export default Search;
also the
Home.js
import React, { useState } from "react";
import { EuiComboBox, EuiText } from "#elastic/eui";
// import { DisplayToggles } from "../form_controls/display_toggles";
import "#babel/polyfill";
import "#elastic/eui/dist/eui_theme_light.css";
import Search from "./Search";
import MunicipioCard from "./MunicipioCard";
const Home = () => {
return (
<div>
<EuiText grow={false}>
<h1>Clima en la provincia de Barcelona</h1>
<h2>Por favor seleccione un municipio</h2>
</EuiText>
<Search />
<MunicipioCard />
</div>
);
};
export default Home;
App.js
import "#babel/polyfill";
import "#elastic/eui/dist/eui_theme_light.css";
import { EuiText } from "#elastic/eui";
import React from "react";
import Home from "./components/Home";
import MunicipiosState from "./contexts/MunicipiosState";
import "./App.css";
function App() {
return (
<MunicipiosState>
<div className="App">
<EuiText>
<h1>App Component h1</h1>
</EuiText>
<Home />
</div>
</MunicipiosState>
);
}
export default App;
You are using forEach and assigning the returned value to a variable, however forEach doesn't return anything. You should instead use map
let municipiosNames = municipiosFromContext.map((municipio) => {
return `label: ${municipio.NOMBRE}`;
});
As per your comment:
you data is loaded asynchronously, so it won't be available on first render and since functional components depend on closures, you onSearchChange function takes the value from the closure at the time of creation and even if you have a setTimeout within it the updated value won't reflect
The solution here is to add municipiosFromContext as a dependency to useEffect
const onSearchChange = useCallback((searchValue) => {
setLoading(true);
setOptions([]);
clearTimeout(searchTimeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
searchTimeout = setTimeout(() => {
// Simulate a remotely-executed search.
setLoading(false);
setOptions(
municipiosNames.filter((option) =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
)
);
}, 1200);
}, [municipiosFromContext]);
useEffect(() => {
// Simulate initial load.
onSearchChange("");
}, [onSearchChange]);
I am trying to develop a custom hook which seems to be pretty easy but I am getting an error
Uncaught Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
This is my hook:
import React, { useState, useEffect } from 'react';
const useInfiniteScroll = (isLastPage: boolean, fetchFn: any) => {
const [pageCount, setPageCount] = useState(0);
const triggerFetchEvents = (): void => {
let response;
setPageCount(() => {
if (!isLastPage) {
response = fetchFn(pageCount + 1, 5, 'latest');
}
return pageCount + 1;
});
return response;
};
useEffect(() => {
triggerFetchEvents();
}, []);
return pageCount;
};
export default useInfiniteScroll;
And the component here I am calling it:
import React, { FC } from 'react';
import { connect } from 'react-redux';
import { fetchEvents } from '../../shared/actions/eventActions';
import { AppState } from '../../shared/types/genericTypes';
import EventModel from '../../shared/models/Event.model';
import EventListPage from '../../components/events/EventListPage';
import useInfiniteScroll from '../../shared/services/triggerInfiniteScroll';
type Props = {
fetchEvents?: any;
isLastPage: boolean;
eventsList?: EventModel[];
};
const mapState: any = (state: AppState, props: Props): Props => ({
eventsList: state.eventReducers.eventsList,
isLastPage: state.eventReducers.isLastPage,
...props
});
const actionCreators = {
fetchEvents
};
export const EventsScene: FC<Props> = props => {
const { eventsList, fetchEvents, isLastPage } = props;
const useIn = () => useInfiniteScroll(isLastPage, fetchEvents);
useIn();
// const [pageCount, setPageCount] = useState(0);
// const triggerFetchEvents = (): void => {
// let response;
// setPageCount(() => {
// if (!isLastPage) {
// response = fetchEvents(pageCount + 1, 1, 'latest');
// }
// return pageCount + 1;
// });
// return response;
// };
// useEffect(() => {
// triggerFetchEvents();
// }, []);
if (!eventsList || !eventsList.length) return null;
return (
<EventListPage
eventsList={eventsList}
isLastPage={isLastPage}
triggerFetchEvents={useIn}
/>
);
};
export default connect(
mapState,
actionCreators
)(EventsScene);
I left the commented code there to show you that if I uncomment the code and remove useInfiniteScroll then it works properly.
What could I be missing?
UPDATE:
This is EventListPage component
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import EventModel from '../../shared/models/Event.model';
import { formatDate } from '../../shared/services/date';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Card from 'react-bootstrap/Card';
type Props = {
eventsList?: EventModel[];
isLastPage: boolean;
triggerFetchEvents: any;
};
export const EventListPage: React.FC<Props> = props => {
const { eventsList, triggerFetchEvents, isLastPage } = props;
const [isFetching, setIsFetching] = useState(false);
const fetchMoreEvents = (): Promise<void> =>
triggerFetchEvents().then(() => {
setIsFetching(false);
});
const handleScroll = (): void => {
if (
document.documentElement.offsetHeight -
(window.innerHeight + document.documentElement.scrollTop) >
1 ||
isFetching
) {
return;
}
return setIsFetching(true);
};
useEffect(() => {
if (isFetching) return;
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
useEffect(() => {
if (!isFetching) return;
if (!isLastPage) fetchMoreEvents();
}, [isFetching]);
if (!eventsList) return null;
return (
<Container className='article-list mt-5'>
///...
</Container>
);
};
export default EventListPage;
In EventsScene, change useInfiniteScroll to be invoked directly at the function body top-level (not sure why you are creating this indirection in the first place):
// before
const useIn = () => useInfiniteScroll(isLastPage, fetchEvents);
useIn();
// after
useInfiniteScroll(isLastPage, fetchEvents)
React expects Hook calls to only happen at the top-level as it relies on the order of Hooks to be always the same. If you wrap the Hook in a function, you can potentially invoke this function in many code locations disturbing the Hooks' order.
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state. Link
I'm trying my hand at TypeScript and React. I have a functional component (code below) that is supposed to consume a context with useContext, but it is showing me this weird error that I cannot find a solution to.
If I do not use TS, and go with JSX, it works just fine.
Edit: Screenshot>
Code:
AppProvider.tsx
import React, { useState, useEffect } from "react";
// Application's context (for general application-wide usage)
const AppContext: any = React.createContext(null);
// this will be used below in the componet we will export
export const AppContextProvider = AppContext.Provider;
export const AppProvider: React.FC = (props: any) => {
const [appName, setAppName] = useState("Blood Donation");
const [appUser, setAppUser]: any = useState(null);
const [appInfoBusy, setAppInfoBusy] = useState(false); // working to get or set data
useEffect(() => {
getAppInfo();
}, []);
const getAppInfo = () => {
setTimeout(() => {
setAppName("Test");
setAppUser({
name: "Admin",
email: "test#test.com",
role_id: 100
});
}, 3000);
};
return (
<AppContextProvider
value={{
appName: appName,
appInfoBusy: appInfoBusy,
appUser: appUser
}}
>
{props.children}
</AppContextProvider>
);
};
Consumer: Login.tsx
import React, { useState, useEffect, useContext } from "react";
import {
Button,
Card,
Elevation,
FormGroup,
InputGroup,
Drawer,
Classes,
H4,
Callout,
H5
} from "#blueprintjs/core";
//#ts-ignore
import ReCAPTCHA from "react-google-recaptcha";
import logo from "../../assets/images/logo.png";
import "../../scss/Login.scss";
import { RecaptchaKey } from "../../shared/Info";
import { AppContextProvider } from "../../shared/context/AppProvider";
const Login: React.FC = props => {
const [email, setEmail]: React.ComponentState = useState();
const [password, setPassword]: any = useState();
const [isOpen, setIsOpen]: any = useState();
const [resetEmail, setResetEmail]: any = useState();
const [emailSent, setEmailSent]: any = useState();
const [captchaOk, setCaptchaOk]: any = useState(false);
const [working, setWorking]: any = useState(false);
// context
const { appName, appUser, appInfoBusy } = useContext(AppContextProvider);
/**
* Handles lifecycle hooks
*/
useEffect(() => {
// when component is mounted
}, []);
/**
* Handles Captcha change
* #param value
*/
const recaptchaChange = (value: any) => {
setCaptchaOk(value ? true : false);
};
const handleRecoverySubmit = () => {
setWorking(true);
setTimeout(() => {
setEmailSent(true);
setWorking(false);
}, 3000);
};
return (
<div id="loginPage">
... removed for brevity ...
</div>
);
};
export default Login;
Any help is gratefully thanked. React and dependencies are all latest as of date.
I was using the context provider instead of the context itself inside useContext(), I should have used useContext(AppContext) instead.
Commentary removed because stackoverflow.
The error is _useContext not defined. The issue is different than what it is actually referring to.
you created a context called as AppContext
and then you export this as
export const AppContextProvider = AppContext.Provider;
You have done correct till this stage.
The problem lies at consumer part i.e. login.tsx file.
you are importing a name file inside a curly braces which is not correct, because the context is exported as a name variable. You simply need to write
import AppContextProvider from "../../shared/context/AppProvider";
That's it, and when you are calling this context using useContext hooks, then the actual state that you are looking for get accessed and no issue will further persist.
Note: Don't use {} for importing named exports.
reference: When should I use curly braces for ES6 import?