I am trying to build an app with an iframe. It will have controllers to change the iframe style and add text to it. Iframe will be like a preview. I want to create the iframe with javascript. I tried this to create the element and append it to component return div like this:
let iframe = document.createElement('iframe');
document.querySelector('#iframeContainer').appendChild(iframe);
ı received this error :TypeError: Cannot read properties of null (reading 'appendChild')
I also tried another method :
let iframe = React.createElement('iframe', {});
ReactDOM.render(
iframe, document.getElementById('root')
);
in this method, because I don't have a return value, it throws an error. I am not able to insert it in a component.
How should I tackle this problem? I am open to all ideas.
I found a solution to this problem. While I was trying to append the element, I missed out on the component lifecycle. After spending good amount of time here is my solution :
const createIframe = () => {
const iframe = document.createElement('iframe');
document.getElementById('iframeContainer').appendChild(iframe);
}
useEffect(() => {
createIframe();
});
By calling my function in useEffect I made sure that div I wanted to append chil is created. I used useEffect but if you're using class component you should use componenDidMount()
Related
In my Reactjs app, I'm using the payment intent API from stripe to handle payments. I use the embeddable Payment Element <PaymentElement /> from #stripe/react-stripe-js to render the UI but the problem is that it takes a couple of seconds before the Payment Element is fully loaded in the UI.
Is there any way I can access its loading state and show a loading spinner while it's being loaded?
Stripe just added a new loader option to their PaymentElement product documented here. It allows you to have a skeleton render first while the UI is loading which should solve the problem you were going for.
Alternatively, you can listen to their ready event documented here so that you show a loading animation until the event is fired.
While this is for their vanilla JS integration, you can use those with their React library since you control which option to pass on the PaymentElement on initialization.
For anyone not content with simply using their loader, you can listen to the ready event (docs).
To do this, you have to get the element first, which is a step that confused me. You should have the elements reference from the useElements hook. In useEffect you can try to do elements.getElement('paymentMethod') but you will get an error saying:
A valid Element name must be provided. Valid Elements are: card,
cardNumber, cardExpiry, cardCvc, postalCode, paymentRequestButton,
iban, idealBank, p24Bank, auBankAccount, fpxBank, affirmMessage,
afterpayClearpayMessage; you passed: paymentElement.
However, the correct thing to get is payment despite not being in that list:
const element = elements.getElement('payment')
element.on('ready', () => {
console.log("READY")
})
Thanks #Yeats on ready solved this for me - great answer.
I want to add for anyone looking at this solution that you should not hide your until the on ready state returns. I mistakenly used a useState variable to show a loader until PaymentElement was ready, but realised that only the loader component needed to be toggled by state and that the PaymentElement should be rendered always. My first try I hid the PaymentElement from render using my loading state var like this:
Don't do this!
{isStripLoading ? (
<MyLoaderComponent />
) : (
<PaymentElement />
)}
So, assuming you have a state variable isStripeLoading default to true and you have useEffect on ready event setIsStripeLoading(false) then wrap only you loader spinner component in the isStripeLoading state variable and NOT the PaymentElement component.
Example
const stripe = useStripe()
const elements = useElements()
const [isStripeLoading, setIsStripLoading] = useState(true)
useEffect(() => {
if (elements) {
const element = elements.getElement('payment')
element.on('ready', () => {
setIsStripLoading(false)
})
}
}, [elements])
return (
<form id='payment-form' onSubmit={handleSubmit}>
{isStripeLoading && <MyLoaderComponent />}
<PaymentElement id='payment-element'/>
<button id='submit' disabled={!stripe || !elements}>Pay</button>
</form>
)
We are using ReactJS framework, so we don't find element written in the entire code. The only way is to find the DOM element and should set the attribute. I am doing this in app.js.
setTimeout(function () {
const formElement = document.getElementsByTagName('form')
formElement.item(0).setAttribute('autocomplete', 'off')
}, 1000)
The above solution is working fine, but if the page loading is slow, then this will not set the attribute. Is there any other way to find the element and set the attribute? I have also tried to set it using the below code after DOMContentLoaded, but it's not working. Nothing inside this event is working.
document.addEventListener('DOMContentLoaded', () => {
const formElement = document.getElementsByTagName('form')
formElement.item(0).setAttribute('autocomplete', 'off')
})
You don't find the element because 'DOMContentLoaded' gets fired when HTML file completes loading, not when React renders the form.
How about using MutationObserver instead? https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Also doing that in root level app.js sounds wrong. Can you do it in the component that is closest to the container of the form?
In React a good way to access DOM elements is by using useRef hook. However, if that element you want to query is not in the JSX of your current component, the useEffect hook could be used with normal JavaScript DOM methods. Like so:
useEffect(()=>{
const formElement = document.getElementsByTagName('form')
formElement.item(0).setAttribute('autocomplete', 'off')
},[]);
I'm creating a react app with useState and useContext for state management. So far this worked like a charm, but now I've come across a feature that needs something like an event:
Let's say there is a ContentPage which renders a lot of content pieces. The user can scroll through this and read the content.
And there's also a BookmarkPage. Clicking on a bookmark opens the ContentPage and scrolls to the corresponding piece of content.
This scrolling to content is a one-time action. Ideally, I would like to have an event listener in my ContentPage that consumes ScrollTo(item) events. But react pretty much prevents all use of events. DOM events can't be caught in the virtual dom and it's not possible to create custom synthetic events.
Also, the command "open up content piece XYZ" can come from many parts in the component tree (the example doesn't completely fit what I'm trying to implement). An event that just bubbles up the tree wouldn't solve the problem.
So I guess the react way is to somehow represent this event with the app state?
I have a workaround solution but it's hacky and has a problem (which is why I'm posting this question):
export interface MessageQueue{
messages: number[],
push:(num: number)=>void,
pop:()=>number
}
const defaultMessageQueue{
messages:[],
push: (num:number) => {throw new Error("don't use default");},
pop: () => {throw new Error("don't use default");}
}
export const MessageQueueContext = React.createContext<MessageQueue>(defaultMessageQueue);
In the component I'm providing this with:
const [messages, setmessages] = useState<number[]>([]);
//...
<MessageQueueContext.Provider value={{
messages: messages,
push:(num:number)=>{
setmessages([...messages, num]);
},
pop:()=>{
if(messages.length==0)return;
const message = messages[-1];
setmessages([...messages.slice(0, -1)]);
return message;
}
}}>
Now any component that needs to send or receive messages can use the Context.
Pushing a message works as expected. The Context changes and all components that use it re-render.
But popping a message also changes the context and also causes a re-render. This second re-render is wasted since there is no reason to do it.
Is there a clean way to implement actions/messages/events in a codebase that does state management with useState and useContext?
Since you're using routing in Ionic's router (React-Router), and you navigate between two pages, you can use the URL to pass params to the page:
Define the route to have an optional path param. Something like content-page/:section?
In the ContentPage, get the param (section) using React Router's useParams. Create a useEffect with section as the only changing dependency only. On first render (or if section changes) the scroll code would be called.
const { section } = useParams();
useEffect(() => {
// the code to jump to the section
}, [section]);
I am not sure why can't you use document.dispatchEvent(new CustomEvent()) with an associated eventListener.
Also if it's a matter of scrolling you can scrollIntoView using refs
What does putting an element inside Svelte Component mean?
Eg. this code:
const target = document.createElement('div');
// render the component in the new element
const sample = new Sample({ target });
Like, here, in the given linked code, author is doing that:
https://github.com/rspieker/jest-transform-svelte/blob/master/example/test/Sample.spec.js#L8
What does this do? Is it putting Svelte component inside a div? Is it a Svelte syntax to put the element inside the constructor of the Svelte component?
Yes, that snippet is initializing the Svelte component named Sample and rendering it within the target div. The target property of a Svelte component constructor's options parameter is the only required property.
For more information, check out Svelte's components documentation.
It is the place where in your document the component will be rendered. Normally you would use a very specific location like body or a div with a certain id.
In this case however you are not actually rendering a page but merely testing a component so it doesn't matter where the div is.
You can find more info on testing with Jest here https://jestjs.io/
i have some problem which i can't figure out an hours..
Here is what:
i'm using react and react router.
I have a some component with own route like /about
and in this component i need to put some 3rd party javascript file which going with getting from some service some iframe to my page.
this service saying to me adding their script and rendering some div with specific id
i put this script to my component in componentDidMount method like that:
componentDidMount() {
var loadScript = function(src) {
var tag = document.createElement("script");
tag.async = false;
tag.src = src;
var body = document.getElementsByTagName("body")[0];
body.appendChild(tag);
};
loadScript("https://somewhere/js?for=myid");
}
and then in my render:
render() {
return (
<div className="content">
<div id="iframe_app"></div>
</div>
);
}
till here everything is normal but this script tag calling just once..
when i change route eg going another page of my app and back this component script not working not appearing in network tab.
But if i'm hard refreshing window with ctrl + r anything works again.
so i don't know why is happening and because of that i think to refresh page on componentDidMount directly with
window.location.reload()
but that time it's working in loop so my page refreshing infinity
how i can figure out with this?
if you really want this - add a unique parameter to the search string like loadScript("https://somewhere/js?for=myid&ver=" + Math.random()); // but you should use your own unique id generator instead of Math.random(), for example - this https://www.npmjs.com/package/uniqid
I suggest you to load the scripts in the constructor before the component is already loaded. It isn’t a best practice use setState from componentDidMount