How do you reload an iframe with ReactJS? - reactjs

My ReactJS component contains an iframe. In response to an event in the outer page, I need to reload the iframe. If the user has navigated to another page in the iframe, I need to reset it to the URL that it had when I first loaded the page. This URL is available in this.props.
I've tried using forceUpdate(). I can see that this causes the render method to run, but the iframe doesn't get reset - presumably because React can't tell that anything has changed.
At the moment I'm appending some random text to the iframe's querystring: this URL change forces React to re-render the iframe. However this feels a bit dirty: the page within the iframe is beyond my control so who knows what this extra querystring value might do?
resetIframe() {
console.log("==== resetIframe ====");
this.forceUpdate();
}
public render() {
console.log("==== render ====");
// How can I use just this.props.myUrl, without the Math.random()?
let iframeUrl = this.props.myUrl + '&random=' + Math.random().toString();
return <div>
<button onClick={() => { this.resetIframe(); }}>Reset</button>
<iframe src={iframeUrl}></iframe>
</div>
}
(I'm using TypeScript too, if that makes a difference.)

I'd create a state variable with the random, and just update it on resetIframe:
state = {
random: 0
}
resetIframe() {
this.setState({random: this.state.random + 1});
}
public render() {
return <div>
<button onClick={() => { this.resetIframe(); }}>Reset</button>
<iframe key={this.state.random} src={this.props.myUrl}></iframe>
</div>
}
Here is a fiddle working: https://codesandbox.io/s/pp3n7wnzvx

In case of Javascript frameworks and technologies, If you update the key, then it'll automatically reload the iFrame. So you just want to increase (or Random number) the value of key for your action.
state = {
iframe_key: 0,
iframe_url: 'https://www.google.com/maps' //Your URL here
}
iframeRefresh() {
this.setState({iframe_key: this.state.iframe_key + 1});
}
public render() {
return (
<div>
<button onClick={() => { this.iframeRefresh(); }}>Reload Iframe</button>
<iframe key={this.state.iframe_key} src={this.state.iframe_url}>
</iframe>
</div>
);
}

<iframe key={props.location.key} />
use This "Router location Key" each click will get random value, whenever to navigate to here this value will get change then iframe will get the refresh

You could create a new component which just loads you iframe. In your main component you load it and give it a update prop which you ignore in you iframe component. If you set the prop with a state variable, your component will reload. You can take a look at Diogo Sgrillo's answer for the setState method.
You could also check with shouldcomponentupdate() if the prop changed and only update if it changed.

Maybe a bit late here :) Just a side note: When you update state based on previous state you should pass a function to setState:
resetIframe() {
this.setState(prevState => {random: prevState.random + 1});
}

Updating the source causes a reload without causing a rerender.
import React, { useState, useRef } from 'react'
function renderIframe({src}){
const iframeRef = useRef()
return <>
<iframe ref={iframeRef} src={src} >
<span onClick={e => (iframeRef.current.src += '')}>Reload</span
</>
}

Related

How to useEffect in component that is rendered in another component

This is what I am trying to do:
import { SVG } from '#svgdotjs/svg.js'
const SVGpaper = (props) => {
useEffect(() => {
let canvas = SVG().addTo('#canvas').size(6006, 600);
canvas.rect(100, 100);
});
return (
<div id="canvas">
</div>
)
}
const Profile = () => {
return (
(typeof someVariable !== "undefined") &&
<CCol>
<CRow>
<div>
{SVGpaper()}
</div>
</CRow>
</CCol>
);
};
export default Profile;
Profile is the "big" webpage, it will fetch some data from a server to "someVariable". To make sure it is not rendered prematurely it (Profile) has conditional rendering. When it is eventually rendered I want to create an SVG. I have therefore created a component that creates the SVG and put it into the render of Profile. Because SVG().addTo must be executed after the div id="canvas" actually exist, it is put in a useEffect to prevent it from executing before the div exist. This works great in my head, but not in reality because it breaks the rules of hooks. I get the error:
"Warning: React has detected a change in the order of Hooks called by Profile. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks"
And I should know this, but I am having issues making this code work, so I am trying a little bit of every idea I get, but without any success. How can I change this code and make it render in this order:
conditional rendering for "someVariable"
render the div with id="canvas"
Create the SVG and assign it to the div with id="canvas"
?
Thank you for your help!

useRef store value and display value in DOM [React]

Because this component changes frequently, I decided to use useRef to store values (for example, a counter). On an event (such as onclick), the log shows that the number did increment by 1 each time the button was clicked, but the value isn't updated on the screen (inside the div). It's showing 0. What am I missing here?
Intended output: on click button, add() increases counter, and display value in <div>.
const counter = useRef(0);
function add() {
counter.current += 1;
console.log(counter.current); // this shows that the number did increment
}
return (
<div>
<div>{counter.current}</div> {/* this shows '0' */}
<button onClick={() => add()}>Add</button>
</div>
);
As you stated in the comments:
I am interacting with this variable in third party library listener functions. These libraries loads on page load, and receiving events from javascript listener.
Means that you want to render component on 3rd party reference change, usually you mock a render like so:
const reducer = p => !p;
const App = () => {
const counter = useRef(0);
const [, render] = useReducer(reducer, false);
function add() {
// Some 3rd party ref
counter.current += 1;
// Render and update the UI
render();
}
return (
<div>
<div>{counter.current}</div>
<button onClick={() => add()}>Add</button>
</div>
);
};
read the doc
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.
using the useState is the best thing in our case

Refreshing Component after Route Change

I have a row of buttons that all links to a chart being rendered, then the button pressed, it decides which data will be shown on the chart below.
<div>
<Route path="/" component={Main} />
<Route path="/chart/:topic" component={Chart} />
</div>
Button element:
<Link to={"/chart/" + collection.name}>
<Button key={index} data-key={index} style={this.btnStyle}>
{this.store.capitalizeFirstLetter(collection.name)}
</Button>
</Link>
This works fine when the button is pressed for the first time. However if the user tries to change the data by pressing a different button the chart component does not refresh at all, browser shows that the URL has changed however the component does not refresh at all.
I know this is because of, I've put a console.log in the chart component and it does not come up the second time a button is pressed.
componentDidMount = () => {
const { match: { params } } = this.props;
this.topic = params.topic;
console.log("chart topic", this.topic);
this.refreshData(true);
this.forceUpdate();
this.store.nytTest(this.topic, this.startDate, this.endDate);
};
As you can see I tried to put a forceUpdate() call but that did nothing. Any help is appreciated!
It's because your component is already rendered and didn't see any change so it don't rerender.
You have to use the componentWillReceiveProps method to force the refresh of your component
Example
componentWillReceiveProps(nextProps){
if(nextProps.match.params.topic){
//Changing the state will trigger the rendering
//Or call your service who's refreshing your data
this.setState({
topic:nextProps.match.params.topic
})
}
}
EDIT
The componentWillReceiveProps method is deprecated.
Now the static getDerivedStateFromProps is prefered when you're source data are coming from a props params
Documentation
This method shoud return the new state for trigger the remounting, or null for no refresh.
Example
static getDerivedStateFromProps(props, state) {
if (props.match.params.topic && props.match.params.topic !== state.topic) {
//Resetting the state
//Clear your old data based on the old topic, update the current topic
return {
data: null,
topic: props.match.params.topic
};
}
return null;
}
componentDidMount is only called once while mounting(rendering) the component.
You should use getDerivedStateFromProps to update your component

react-router redirect to a different domain url

I am using react-router for client side routing. I have a button and when some one clicks the button, I want to redirect the user to a different url.
For e.g I want to redirect the user to "http://www.google.com". I used navigation mixin and used this.transitionTo("https://www.google.com"). But when I do this I get this error
Invariant Violation: Cannot find a route named "https://www.google.com".
I can use window.location but is that the right way to go?
As pointed out in the comments to this answer, default way of solving this would be to use anchor element (the a tag) with href attribute that points at the destination URL that you'd like to route the user to. A button that has appearance of a button but behavior or an anchor is pretty much a web anti-pattern. See more info in this answer: https://stackoverflow.com/a/1667512/1460905.
That said, there certainly is a potential scenario when a web app needs to perform some action and only then redirect the user. In this case, if primary action the user takes is submitting some data or really performing an action, and redirect is more of a side-effect, then the original question is valid.
In this case, why not use location property of window object? It even provides a nice functional method to go to external location. See the ref.
So, if you have a component, say
class Button extends Component {
render() {
return (
<button onClick={this.handleClick.bind(this)} />
);
}
}
then add handleClick that would make the component look like
class Button extends Component {
handleClick() {
// do something meaningful, Promises, if/else, whatever, and then
window.location.assign('http://github.com');
}
render() {
return (
<button onClick={this.handleClick.bind(this)} />
);
}
}
No need to import window since it's global. Should work perfectly in any modern browser.
Also, if you have a component that is declared as a function, you may possibly use the effect hook to change location when state changes, like
const Button = () => {
const [clicked, setClicked] = useState(false);
useEffect(() => {
if (clicked) {
// do something meaningful, Promises, if/else, whatever, and then
window.location.assign('http://github.com');
}
});
return (
<button onClick={() => setClicked(true)}></button>
);
};
You don't need react-router for external links, you can use regular link elements (i.e. <a href="..."/>) just fine.
You only need react-router when you have internal navigation (i.e. from component to component) for which the browser's URL bar should make it look like your app is actually switching "real" URLs.
Edit because people seem to think you can't use an <a href="..." if you need to "do work first", an example of doing exactly that:
render() {
return <a href={settings.externalLocation} onClick={evt => this.leave(evt)}/>
}
async leave(evt) {
if (this.state.finalized) return;
evt.preventDefault();
// Do whatever you need to do, but do it quickly, meaning that if you need to do
// various things, do them all in parallel instead of running them one by one:
await Promise.all([
utils.doAllTheMetrics(),
user.logOutUser(),
store.cleanUp(),
somelib.whatever(),
]);
// done, let's leave.
this.setState({ finalized: true }), () => evt.target.click());
}
And that's it: when you click the link (that you styled to look like a button because that's what CSS is for) React checks if it can safely navigate away as a state check.
If it can, it lets that happen.
If it can't:
it prevents the navigation of occurring via preventDefault(),
does whatever work it needs to do, and then
marks itself as "it is safe to leave now", then retriggers the link.
You can try and create a link element and click it from code. This work for me
const navigateUrl = (url) => {
let element = document.createElement('a');
if(url.startsWith('http://') || url.startsWith('https://')){
element.href = url;
} else{
element.href = 'http://' + url;
}
element.click();
}
As pointed by #Mike 'Pomax' Kamermans, you can just use to navigate to external link.
I usually do it this way, with is-internal-link
import React from 'react'
import { Link as ReactRouterLink} from 'react-router-dom'
import { isInternalLink } from 'is-internal-link'
const Link = ({ children, to, activeClassName, ...other }) => {
if (isInternalLink(to)) {
return (
<ReactRouterLink to={to} activeClassName={activeClassName} {...other}>
{children}
</ReactRouterLink>
)
}
return (
<a href={to} target="_blank" {...other}>
{children}
</a>
)
}
export default Link
Disclaimer: I am the author of this is-internal-link
I had the same issue and my research into the issue uncovered that I could simply use an "a href" tag. If using target="_blank" you should write your link this...
Your Link
I couldn't find a simple way to do that with React Router. As #Mike wrote you should use anchor (<a> tags) when sending the user to external site.
I created a custom <Link> component to dynamically decide whether to render a React-Router <Link> or regular <a> tag.
import * as React from "react";
import {Link, LinkProps} from "react-router-dom";
const ReloadableLink = (props: LinkProps & { forceReload?: boolean }) => {
const {forceReload, ...linkProps} = props;
if (forceReload)
return <a {...linkProps} href={String(props.to)}/>;
else
return <Link {...linkProps}>
{props.children}
</Link>
};
export default ReloadableLink;

How can I reset a react component including all transitively reachable state?

I occasionally have react components that are conceptually stateful which I want to reset. The ideal behavior would be equivalent to removing the old component and readding a new, pristine component.
React provides a method setState which allows setting the components own explicit state, but that excludes implicit state such as browser focus and form state, and it also excludes the state of its children. Catching all that indirect state can be a tricky task, and I'd prefer to solve it rigorously and completely rather that playing whack-a-mole with every new bit of surprising state.
Is there an API or pattern to do this?
Edit: I made a trivial example demonstrating the this.replaceState(this.getInitialState()) approach and contrasting it with the this.setState(this.getInitialState()) approach: jsfiddle - replaceState is more robust.
To ensure that the implicit browser state you mention and state of children is reset, you can add a key attribute to the root-level component returned by render; when it changes, that component will be thrown away and created from scratch.
render: function() {
// ...
return <div key={uniqueId}>
{children}
</div>;
}
There's no shortcut to reset the individual component's local state.
Adding a key attribute to the element that you need to reinitialize, will reload it every time the props or state associate to the element change.
key={new Date().getTime()}
Here is an example:
render() {
const items = (this.props.resources) || [];
const totalNumberOfItems = (this.props.resources.noOfItems) || 0;
return (
<div className="items-container">
<PaginationContainer
key={new Date().getTime()}
totalNumberOfItems={totalNumberOfItems}
items={items}
onPageChange={this.onPageChange}
/>
</div>
);
}
You should actually avoid replaceState and use setState instead.
The docs say that replaceState "may be removed entirely in a future version of React." I think it will most definitely be removed because replaceState doesn't really jive with the philosophy of React. It facilitates making a React component begin to feel kinda swiss knife-y.
This grates against the natural growth of a React component of becoming smaller, and more purpose-made.
In React, if you have to err on generalization or specialization: aim for specialization. As a corollary, the state tree for your component should have a certain parsimony (it's fine to tastefully break this rule if you're scaffolding out a brand-spanking new product though).
Anyway this is how you do it. Similar to Ben's (accepted) answer above, but like this:
this.setState(this.getInitialState());
Also (like Ben also said) in order to reset the "browser state" you need to remove that DOM node. Harness the power of the vdom and use a new key prop for that component. The new render will replace that component wholesale.
Reference: https://facebook.github.io/react/docs/component-api.html#replacestate
The approach where you add a key property to the element and control its value from the parent works correctly. Here is an example of how you use a component to reset itself.
The key is controlled in the parent element, but the function that updates the key is passed as a prop to the main element. That way, the button that resets a form can reside in the form component itself.
const InnerForm = (props) => {
const { resetForm } = props;
const [value, setValue] = useState('initialValue');
return (
<>
Value: {value}
<button onClick={() => { setValue('newValue'); }}>
Change Value
</button>
<button onClick={resetForm}>
Reset Form
</button>
</>
);
};
export const App = (props) => {
const [resetHeuristicKey, setResetHeuristicKey] = useState(false);
const resetForm = () => setResetHeuristicKey(!resetHeuristicKey);
return (
<>
<h1>Form</h1>
<InnerForm key={resetHeuristicKey} resetForm={resetForm} />
</>
);
};
Example code (reset the MyFormComponent and it's state after submitted successfully):
function render() {
const [formkey, setFormkey] = useState( Date.now() )
return <>
<MyFormComponent key={formkey} handleSubmitted={()=>{
setFormkey( Date.now() )
}}/>
</>
}
Maybe you can use the method reset() of the form:
import { useRef } from 'react';
interface Props {
data: string;
}
function Demo(props: Props) {
const formRef = useRef<HTMLFormElement | null>(null);
function resetHandler() {
formRef.current?.reset();
}
return(
<form ref={formRef}>
<input defaultValue={props.data}/>
<button onClick={resetHandler}>reset</button>
</form>
);
}

Resources