I am working on a React application.
I have a sort of card with image, title and a button to do stuff.
This is a simplified version:
<a href="www.example.com" class="card">
<div class="image-wrapper">
<div class="image">...</div>
<div class="cta-button" onClick={handleOnClick}>cta</div>
</div>
<div class="title">title</div>
</a>
cta-button is placed on bottom-right of image-wrapper using position: absolute.
How can I, when clicking on cta-button, prevent visiting card link in handleOnClick?
Having an interactive element within an a element is a violation of the specification. An a element cannot have interactive content. While your div is just a div and so it's not interactive by default, it is interactive if you add a click handler to it. I suggest moving the clickable element outside the a element.
But if you want to have the invalid structure, you need to prevent the default action of the click. Have handleClick accept its event parameter and call event.preventDefault() on it:
const {useState} = React;
const Example = () => {
const handleClick = (e) => {
console.log("Stopped");
e.preventDefault();
};
return <div onClick={handleClick}>Click here</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
For completeness, here's that same snippet with stopPropgation instead (which doesn't work):
const {useState} = React;
const Example = () => {
const handleClick = (e) => {
console.log("Stopped");
e.stopPropagation(); // Doesn't work
};
return <div onClick={handleClick}>Click here</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Related
When I run the code below I see different behavior in the sandbox I'm using. When I click the button I get "Uncaught SyntaxError: function statement requires a name" in the console but here it is rendering the click event out as text in the button (not sure why?)
Question is—is there a way to pass functions like this to elements created in template literals that are added to the DOM using innerHTML?
const clickHandler = () => {
console.log("Hi");
};
const root = document.getElementById("root");
root.innerHTML = `<button onClick=${clickHandler}>Click</button>`;
<!DOCTYPE html>
<html>
<body>
<div id="root"></div>
</body>
</html>
Inline handlers, being attributes, are essentially strings. When you do
<button onClick=${clickHandler}>
the function gets turned into a string, which results in:
<button onclick="()" ==""> {
console.log("Hi");
}>Click</button>
which isn't what you want - the automatic fixing of the syntax caused by the interpolation doesn't work well.
When you do it properly, the string in the attribute, when evaluated as JavaScript, may only reference global variables (for all the cases worth mentioning). While you can get your code to work by using the name of the function, and by making sure the function is global:
const clickHandler = () => {
console.log("Hi");
};
const root = document.getElementById("root");
root.innerHTML = `<button onClick=clickHandler()>Click</button>`;
<div id="root"></div>
A much better approach would be to attach the event listener with JavaScript, instead of an eval-like attribute with scope problems. Insert the elements, then navigate to the element you want the listener attached to, and use addEventListener.
const clickHandler = () => {
console.log("Hi");
};
const root = document.getElementById("root");
root.innerHTML = `<button>Click</button>`;
root.querySelector('button').addEventListener('click', clickHandler);
<div id="root"></div>
Or use a framework designed for this sort of thing.
const App = () => {
const clickHandler = () => {
console.log("Hi");
};
return <button onClick={clickHandler}>Click</button>;
};
ReactDOM.createRoot(document.querySelector('.root')).render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div class='root'></div>
Is there any way to add all the events from the parent element to all the child elements using ReactJS without hard coding all of the events?
<div className='myClass' onClick={handleSelect} onDoubleClick={handleOpen}>
<span className='child' />
</div>
Click handlers propagate, so there's no need. Attach a single handler to the parent.
const App = () => {
const handleSelect = () => console.log('click');
const handleOpen = () => console.log('double click');
return (
<div className='myClass' onClick={handleSelect} onDoubleClick={handleOpen}>
<span className='child'>child</span>
</div>
);
};
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
If you need to identify which child element was clicked on, or exclude certain children from resulting in the handler running, use the event passed to the handler and see if its target .matches what you're looking for.
I am trying to create scroll effect, where on left side image is changing based on scroll position and right side is changing content while scrolling.
I am getting error when trying to execute onscroll javascript function. I am not sure what am I doing wrong - I am trying to use it in React App - is it possible that React has different rules to call this function?
I created example in here:
https://codesandbox.io/s/scroll-effect-37ecc
I would use useRef for dom manipulation in React. That will be optimized.
import { useLayoutEffect, useState, useRef, useEffect } from "react";
import { render } from "react-dom";
import classnames from "classnames";
import "./index.css";
const App = () => {
// const scrollingDiv = document.getElementById("scrollContainer");
// const img1 = document.getElementById("img1");
// const img2 = document.getElementById("img2");
const scrollingDiv = useRef(null);
const image1 = useRef(null);
const image2 = useRef(null);
useEffect(() => {
scrollingDiv.current.onscroll = function () {
if (scrollingDiv.scrollTop < 250) {
image1.current.src = "https://placeimg.com/250/100/arch";
image2.current.src = "https://placeimg.com/250/100/animals";
}
if (scrollingDiv.current.scrollTop > 500) {
image1.current.src = "https://placeimg.com/250/100/nature";
image2.current.src = "https://placeimg.com/250/100/people";
}
if (scrollingDiv.current.scrollTop > 1000) {
image1.current.src = "https://placeimg.com/250/100/tech";
image2.current.src = "https://placeimg.com/250/100/any";
}
};
}, []);
return (
<>
<div class="container">
<div class="left">
<img ref={image1} id="img1" src="https://placeimg.com/250/100/arch" />
<img ref={image2} id="img2" src="https://placeimg.com/250/100/animals" />
</div>
<div class="middle" ref={scrollingDiv} id="scrollContainer">
<div class="in-middle">
<div class="in-in-middle" id="1"></div>
<div class="in-in-middle" id="2"></div>
<div class="in-in-middle" id="3"></div>
</div>
</div>
</div>
</>
);
};
render(<App />, document.getElementById("root"));
Or you can put direct listeners on the element as well using onSroll prop.
Also, don't forget to clear all the listeners in useEffect cleaning function, that way, you won't have any memory leak issues.
normally, you would use useRef() function from react to get direct access to DOM elements, you can then use them like this:
const scrollingDiv = useRef(null);
<div id="scrollingSomething" ref={scrollingDiv}>
then you can manipulate the element using scollingDiv reference to the element
https://codesandbox.io/s/scroll-effect-forked-3nkbb
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>
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>
);
}