In my site, I need to send information (via postMessage) to an iFrame. I know in regular Javascript, I would accomplish this by using document.getElementById or $("#iframe") in JQuery to select the iframe. However, I am unsure of how to do this in ReactJS. Is there a specific way of doing this in ReactJS/NextJS that I just don't know about? I need access to the iframe (the child component) from its container (parent component).
If the iframe is rendered by React, and only the component that renders it (or its descendants) needs to access it, then typically you use refs.
If the iframe is always on the page, or rendered in some way outside of React, it's perfectly fine to get it via document.getElementById, document.querySelector, or other DOM methods.
Here's an example of using a ref in a functional component via useRef, but you can do the same thing (in a different way) in a class component via createRef. I'll use a div instead of an iframe, but it's the same for iframes.
function Example() {
const ref = React.useRef(null);
const doSomething = () => {
if (ref.current) {
console.log(`The div's text is "${ref.current.textContent}"`);
} else {
console.log("The div doesn't exist");
}
};
return (
<div>
<div ref={ref}>
This is the target div
</div>
<input type="button" onClick={doSomething} value="Click Me" />
</div>
);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
Class component example:
class Example extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
this.doSomething = this.doSomething.bind(this);
}
doSomething() {
if (this.ref.current) {
console.log(`The div's text is "${this.ref.current.textContent}"`);
} else {
console.log("The div doesn't exist");
}
}
render() {
return (
<div>
<div ref={this.ref}>
This is the target div
</div>
<input type="button" onClick={this.doSomething} value="Click Me" />
</div>
);
}
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
Related
I try to add a JS script to an React component, nevertheless nothing append.
The message Hello is displayed but not the script.
The swcript is to the same repository that the component Canvas.
I try to add it like this:
class Accueil extends Component {
render() {
return (
<div>
<Canvas/>
</div>
);
}
}
class Canvas extends Component {
componentDidMount() {
const script = document.createElement("script");
script.async = true;
script.src = "./Sketch.js";
this.div.appendChild(script);
}
render() {
return (
<div className="App" ref={el => (this.div = el)}>
<h1>Hello</h1>
{/* Script is inserted here */}
</div>
);
}
}
But when I insert directly the script Sketch in top of the page
import {Sketch} from './Sketch.js'
The script is well add but it is add also for other components, so I think it is not the well way to do it.
Thanks in advance :)
If you dynamically append a script to body or anything inside body, it will not get executed (because everything there executes only once during page load. You have to append it to head.
You can use global variables help it 'find its component' or vice versa...
Hello I am trying to add another container div on click. With my current code I am getting the error TypeError: document.getElementById(...) is null. I got this answer from a stackoverflow problem and I beleive it does not work because I am using react/gatsby.
I have also tried having the <button id="click"></button> inside of trello.js and I am still getting the same error.
Trello.js:
import React from 'react'
import "./style.css"
import Button from "./Button"
export default function Trello() {
const draggables = document.querySelectorAll('.draggable')
const containers = document.querySelectorAll('.container')
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', () => {
draggable.classList.add('dragging')
})
draggable.addEventListener('dragend', () => {
draggable.classList.remove('dragging')
})
})
containers.forEach(container => {
container.addEventListener('dragover', () => {
const draggable = document.querySelector('.dragging')
container.appendChild(draggable)
})
})
return (
<div>
<Button />
<div id="main-div">
<div>
<div class="draggable" draggable="true">Jason</div>
<div class="draggable" draggable="true">Jack</div>
<div class="draggable" draggable="true">Caleb</div>
<div class="draggable" draggable="true">Ransom</div>
</div>
<body>
<div class="container">
<p></p>
</div>
<div class="container">
<p></p>
</div>
<div class="container">
<p></p>
</div>
</body>
</div>
</div>
)
}
Button.js
import React from 'react'
export default function Button() {
document.getElementById('click').onclick = function () {
var div = document.createElement('div')
div.className = 'container'
document.getElementsByTagName('body')[0].appendChild(div)
}
return (
<div>
<button id="click"></button>
</div>
)
}
You are trying to access document (to get the identifier) on processing render time where it isn't defined yet, as Gatsby documentation explains:
Some of your code references “browser globals” like window or
document. If this is your problem you should see an error above like
“window is not defined”.
That's the reason why it is null in your snippet. If you want to do this, you may need to use a useEffect/componentDidMount hook/lifecycle. Both methods await and trigger once the component output has been rendered to the DOM.
However, accessing directly to the DOM in React is strongly not recommended, indeed, that's why you are using React, to create and manipulate a virtual DOM, where the cost (in terms of efficiency) for the browser to render and rerender it, instead of changing itself is the great value of React. Of course, it is not prohibited but you should avoid it. Instead, you can use useRef hook.
The last thing is that you may want to use something like this to achieve your goal is something like this:
import React from 'react'
export default function Button() {
const createDiv =()=> <div className={`container`}>'Im a new div</div>
return (
<div>
<button onClick={createDiv}>Click me</button>
</div>
)
}
The snippet above will create a new <div>, with "I'm a new div" inside as a return of your onClick function. If you need to create a new <div>on every click, the snippet needs a few changes, let me know and I'll update it.
import React from "react";
export default function Button() {
let click = () => {
var div = document.createElement("div");
div.innerHTML = "New DIV";
div.className = "container";
document.getElementsByTagName("body")[0].appendChild(div);
};
return (
<div>
<button onClick={click}>Click Me</button>
</div>
);
}
I'm trying to change the text of a <p> to David and Larry accordingly when each button (that has an image inside) has hovered. I have experimented with numerous things and found a way to change the CSS of the button with a function. But I was unable to find anything to change text since <p> is in a different class. Any suggestions to address this problem?
For your information, I have added a CSS color changing function I used earlier to the below code sample.
here's my code.
import React from 'react';
import "./Tri.css";
function Tri() {
function davidon(e) {
e.target.style.background = 'red';
}
function davidoff(e) {
e.target.style.background = 'green';
}
function larryon(e) {
e.target.style.background = 'red';
}
function larryoff(e) {
e.target.style.background = 'green';
}
return (
<div>
<div>
<div>
<button onMouseOver={davidon} onMouseLeave={davidoff}>
<img src={require(`./images/david.png`)} className="david"/>
</button>
<button onMouseOver={larryon} onMouseLeave={larryoff}>
<img src={require(`./images/larry.png`)} className="larry"/>
</button>
</div>
<div className="plex">
<p>Larry Or David?</p>
</div>
</div>
</div>
);
}
export default Tri;
Thanks in advance for you replies.
You need to think more in "React", and use component state and props. The offical documentation is a good place to start.
Here I've got two components.
1) Tri: which has it's own state, and builds the HTML using Button components
2) Button: since you need each button to change color depending on the mouse action it's best to separate that functionality out into a new component so that each instance can have its own state.
(I've intentionally left out the images in this example, but you could pass in a src prop to the button and have that handle the images too if you wanted.)
const { useState } = React;
// `Button` accepts a props object
// Here I've destructured out the button name,
// and the handleHover function
function Button({ name, handleHover }) {
// We initialise the state with "green"
const [ color, setColor ] = useState('green');
function handleColor() {
// We set the new color based on the current color
setColor(color => color === 'red' ? 'green' : 'red');
// And then call the `handleHover` function, passing in `name`
handleHover(name);
}
return (
<button
className={color}
onMouseOver={handleColor}
onMouseLeave={handleColor}
>
{name}
</button>
);
}
function Tri() {
// In `Tri` we set its own state for the name
// initialised to an empty string
const [ name, setName ] = useState('');
// A handler that changes the name
// This is the function we pass to each button
function handleHover(name) {
setName(name);
}
// Set up two buttons using our Button component
// assigning a name to each, and passing in our handler
// Whenever the name (state) is changed the name in the
// paragraph also changes
return (
<div>
<div>
<Button name="Larry" handleHover={handleHover} />
<Button name="David" handleHover={handleHover} />
</div>
<p>{name}</p>
</div>
);
}
ReactDOM.render(
<Tri />,
document.getElementById('react')
);
.red { background-color: red; }
.green { background-color: green; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Try using states. And don't change DOM-nodes dynamically in event handlers. Always use React functionality:
React uses a declarative form of programming (The Virtual DOM specifically). You define variables and set them and React updates the DOM if those change.
useState gives you the opportunity to declare an editable (through a setter function) variable. See Docs on State and Props.
import React from 'react';
import "./Tri.css";
function Tri(props) {
// props as immutable arguments (if needed)
// useState keeps an internal state in the component
let initialTxt = 'Larry Or David?';
const [text, setText] = React.useState(initialTxt);
return (
<div>
<div>
<div>
<button
className="david-btn"
onMouseOver={() => setText('David')}
onMouseLeave={() => setText(initialTxt)}>
<img src={require(`./images/david.png`)} className="david"/>
</button>
<button
className="larry-btn"
onMouseOver={() => setText('Larry')}
onMouseLeave={() => setText(initialTxt)}>>
<img src={require(`./images/larry.png`)} className="larry"/>
</button>
</div>
<div className="plex">
<p>{text}</p>
</div>
</div>
</div>
);
}
Also, extend ./Tri.css with the following code. You could use a style-variable but that would make your code more bloated and unreadable if you have access to CSS.
.david-btn,
.larry-btn {
background-color: green;
}
.david-btn:hover,
.larry-btn:hover {
background-color: red;
}
You are looking for Refs. You can read more about them in documentation.
I've created a simple example (based on your code).
Step by step what I did:
import useRef hook which is used to create reference.
import React, { useRef } from "react";
created reference:
const pTagRef = useRef();
passed reference to your p tag
<div ref={pTagRef} className="plex">
<p>Larry Or David?</p>
</div>
created function which can change the content of this reference where pTagRef.current is DOM element.
function setName(name) {
if (pTagRef.current) {
pTagRef.current.innerText = name;
}
}
called the function whenever name changed
setName("larry");
You should definitely use state for this but I hope this one helps you to get started.
I have a React app which has this code:
<div className={ mainCss } data-appmode={ AppMode.MAIN } onClick={ this.handleAppModeClick.bind(this) }>
<div className="NavLinkIconContainer"></div>
<div>Main</div>
</div>
When I click on div with className="NavLinkIconContainer" the handleAppModeClick function does not work correctly. The function fires but does not pick up the data-* attribute. However, when I click on the div with Main as it's content it works perfectly picking up the data-* attribute. Why is this and how do I fix it?
You can the data-appmode value from event.currentTarget.dataset.
event - the event object
currentTarget - the element with the onClick
dataset - an easy access to data-* values
class Demo extends React.Component {
handleAppModeClick = event => console.log(event.currentTarget.dataset.appmode);
render() {
return (
<div
data-appmode="example"
onClick={this.handleAppModeClick}>
<div className="NavLinkIconContainer">NavLinkIconContainer</div>
<div>Main</div>
</div>
)
}
}
ReactDOM.render(
<Demo />,
root
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Ori Drori's suggestion will work. Alternatively, you can write your component as
<div className={ mainCss } data-appmode={ AppMode.MAIN } onClick={ ()=>this.handleAppModeClick(AppMode.MAIN) }>
<div className="NavLinkIconContainer"></div>
<div>Main</div>
This will fire off the function with a hard-coded argument. In my opinion, it's a little bit cleaner than using event.currentTarget.dataset. That being said, I am sure that others disagree with me, and if the solution works, it works.
Alternatively, you can use
handleAppModeClick = event => console.log(event.target.getAttribute('data-appmode'));
to get the data from the actual element.
In the following example WrapperComp needs to get access to the dom node of the divs in line 5 and line 8, without adding logic to PageComp or ItemComp. The only things I could change in PageComp are the div tags. E.g. I could add a ref, prop, data-attribute, etc to them.
The divs don't have to be created inside PageComp. WrapperComp would be allowed to create them too, but they must wrap each of its children (In this case each ItemComp).
Example
class PageComp extends React.Component {
render() {
return (
<WrapperComp>
<div>
<ItemComp/>
</div>
<div>
<ItemComp/>
</div>
</WrapperComp>
);
}
}
class WrapperComp extends React.Component {
render() {
return (
<div>
<h1>A wrapper</h1>
{this.props.children}
</div>
);
}
}
class ItemComp extends React.Component {
render() {
return (
<div>
<h1>An item</h1>
</div>
);
}
}
ReactDOM.render(
<PageComp/>,
document.getElementById('app')
);
JSBIN
What I tried so far:
I already tried to put a ref= on the divs, but that ref would only be available in PageComp not in WrapperComp.
I also tried to create the divs inside WrapperComp and put a ref= on them from there, but that would result in a Refs Must Have Owner Warning
Now I wonder.. what would be an appropriate way in react to solve that problem?
Till now the only solution that came to my mind was to put a data-attribute on each div and search the dom for them after componentDidMount like that: document.querySelectorAll('[data-xxx]'). Perhaps I'm not sure if this is how you do it in react..
Why do I want to get the node inside WrapperComp?
I want to create a component that adjusts the dimensions of its children. In the example that component would be WrapperComp. The adjustments can only be done after the children rendered to the dom, e.g. to get clientHeight.
If you don't restrict that this needs to be solved by how one should get the DOM, pass them down, etc, I would get rid of the puzzle and approach it in a different direction.
Since you are not given much control to <PageComp> whereas <WrapperComp> seems flexible, I would do the wrapping in the later by transforming the passed children to what you need them to be.
class PageComp extends React.Component {
render() {
return (
<WrapperComp>
<ItemComp/>
<ItemComp/>
</WrapperComp>
);
}
}
class WrapperComp extends React.Component {
render() {
const wrappedChldren = React.Children.map(this.props.children, function(child) {
return (
<div ref={function(div) {
this.setState{clientHeight: div.clientHeight}
}}>
<h1>A wrapper</h1>
{ child }
</div>
);
});
return <div>{ wrappedChildren }</div>;
}
}
With this concentrate can be put on the transformation in the <WrapperComp>, which is pretty intuitive as its name suggests.