get iframe elements in react using ref - reactjs

I have a component contains iframe and I want to access its content in react, I used ref to handle the iframe, how can I get all anchors tags from the iframe
here is my code :
const GridGenerator = () => {
const [loading, setLoading] = useState(true);
const gridIframe = useRef(null);
function handleIframe() {
setLoading(false);
const iframeItem = gridIframe.current;
const anchors = iframeItem.contentWindow.getElementsByTagName("a");
}
return (
<div>
{loading ? <div className={styles.loader} /> : null}
<iframe
title="Grid Generator"
ref={gridIframe}
src="https://collectearth.users.earthengine.app/view/collect-earth-grid-generator"
width="100%"
height="1000px"
frameBorder={0}
onLoad={handleIframe}
/>
<Link to={routes.HOME}>Go Back</Link>
</div>
);
};
so, it works well until :
const iframeItem = gridIframe.current;
and it returns iframe tag, but this line does not work well
const anchors = iframeItem.contentWindow.getElementsByTagName("a");
any solution ? Thank you

You need to access the document of contentWindow in order to get the elements, Window interface does not have any method called getElementsByTagName.
So, instead of
const anchors = iframeItem.contentWindow.getElementsByTagName("a");
you should do
const anchors = iframeItem.contentWindow.document.getElementsByTagName("a");

For me adding to Window worked, instead of Document:
Without bind():
function handleSubmit(e) {
alert('yay 1!', e);
}
iframeRef.current?.contentWindow?.addEventListener('click', handleSubmit, false);
With bind():
function handleSubmit2() {
alert('yay 2!', this.ele);
}
iframeRef.current?.contentWindow?.addEventListener('click', handleSubmit2.bind({ ele: iframeRef }), false);

Related

Event Handling in React js

const [show, setShow] = useState(true)
const showDiv = () => {
if (show) {
document.getElementsByClassName("skills-hero")[0].style.display = "flex"
}
else if (!show) {
document.getElementsByClassName("skills-hero")[0].style.display = "none"
}
}
This is simple logic but it is not working and not giving any error too.
I just don't under stand why it doesn't working
There are two possibilities why your code is not working:
The skill-hero element is defined inside the 'root' element (where you are attaching your Reactjs app. assuming you are attaching your react app to the real DOM. Hence the element is getting overridden by the react app.
function showDiv is not returning anything, I presume react will complain if you call the function inside the return(jsx) function.
To make your example work:
Inside Index.html
<div class="skills-hero">
<div class="p">1</div>
<div class="p">2</div>
</div>
<div id="root"></div>
Inside Javascript
const MyFunc = () => {
const [show, setShow] = React.useState(true);
const showDiv = () => {
if (show) {
document.getElementsByClassName("skills-hero")[0].style.display = "flex";
} else if (!show) {
document.getElementsByClassName("skills-hero")[0].style.display = "none";
}
return "some dummy text";
};
return showDiv();
};
ReactDOM.render(<MyFunc />, document.getElementById("root"));
Note: This method frowned upon. ^^^
You should avoid direct DOM manipulation in ReactJs mainly because:
performance
security (XSS)
and ReactDOM does the dom-manipulation for you efficiently.
it makes out lives way easier 😼
etc...
const [show, setShow] = useState(true);
const showDiv = () => show ? <div className="skills-hero"> </div> : null
You can't select jsx elements with getElementsByClassName because they are not in the real dom. Your real DOM is empty in React.
You might want to do conditional rendering instead;
show && <div className="skills-hero"></div>
something like this.

Hide Element in React Based on Routing

I wanted to hide the RightMenu if the routes are /jobs and /account.
My problem is that if /jobs and /account have children routes like /jobs/1 or /accounts/2. How would i include it? Is there way to hide this? Is there a better way than my code?
UPDATE
What about if we wanted to show the RightMenu on /jobs/new BUT not on /jobs/1? Something like that.
Pls check my codesandbox here CLICK HERE
const noRightMenusRoutes = ["/jobs", "/account"];
const noRightMenus = () => {
return (
noRightMenusRoutes.findIndex((el) => el.includes(location.pathname)) !==
-1
);
};
The best way is to use a state that will be updated when the path name changes :
import React, { Suspense, lazy, useState, useEffect } from "react";
....
function PagesRoute({ match: { url } }) {
const classes = useStyles();
const location = useLocation();
const [showMenu, setShowMenu] = useState(true);
const [noRightMenusRoutes, setNoRightMenusRoutes] = useState([
"/jobs",
"/account"
]);
useEffect(() => {
console.log(location.pathname);
const noRightMenus = noRightMenusRoutes.findIndex((el) => {
return location.pathname.includes(el);
});
setShowMenu(noRightMenus === -1);
}, [location, noRightMenusRoutes]);
...
<Grid item xl={3} lg={3} md={3}>
{!showMenu ? null : <RightMenu />}
</Grid>
You shouldn't use functions inside the JSX for rendering purposes, the functions could be used as event handlers.
React router's matchPath function is a really handy utility to handle these things
You can do something like this
const noRightMenusRoutes = ["/jobs", "/account"];
const isMatching = noRightMenusRoutes.some(path => {
const match = matchPath(location.pathname, {
path: path,
exact: true
})
return match && match.isExact
})
Using this will also help you with paths with parameters such as /jobs/:jobId

I am using React Hook correctly?

I want to apply React hook for infinite scroll. So, I end up with this code:
export default function index() {
const [allTracks, setAllTracks] = useState([]);
const [offset, setOffset] = useState("");
const { tracks, error, loading, lastVisible } = useFetchPublicTracks(
myApiEndPoint.TRENDING_TRACKS,
5,
offset
);
//concat tracks when got data successfully
useEffect(() => {
if (!loading && tracks) setAllTracks([...allTracks, ...tracks]);
}, [loading, tracks]);
console.log("tracks", allTracks);
console.log("lastVisible", lastVisible);
console.log("loading", loading);
return (
<div>
<button className="" onClick={() => setOffset(lastVisible)}>
Load More
</button>
<Main></Main>
</div>
);
}
When I click "Load More" button, new offset will be setted. After that, component re-render again and call Api with new offset.
I want ask that I am using React hook correctly ? There is any way better ? Also, Do I need to use useCallback/useMemo in my use case ?

Can't get ref.current.offsetWidth

I've trying to make contextMenu.
I want to get offsetWidth and offsetHeight from ref.current, but console.log prints undefined.
const ContextMenu: React.FC<ContextMenuProps> = props => {
const thisComponent = useRef(null);
const [isVisible, setIsVisible] = useState<boolean>(false);
let thisComponentHeight = null;
let thisComponentWidth = null;
useEffect(() => {
document.addEventListener("contextmenu", contextMenuHandler);
if (thisComponent && thisComponent.current) {
thisComponentWidth = thisComponent.current;
thisComponentHeight = thisComponent.current;
console.log(thisComponent.current)
}
}, []);
return (
<Column ref={thisComponent}>
<div>test</div>
<div>test2</div>
</Column>
);
};
export default ContextMenu;
This is the picture of console.log(thisComponent.current);
That Column component looks like it belongs to another library, not something native to React, so they might have defined their own set of properties in the current object. Just wrap the Column in a div or span, then give that markup the ref. you will be able to get the offsetWidth as well as the native DOM properties.
return (
<div ref={thisComponent}>
<Column>
<div>test</div>
<div>test2</div>
</Column>
</div>
);

How to target a specific item to toggleClick on using React Hooks?

I have a navbar component with that actual info being pulled in from a CMS. Some of the nav links have a dropdown component onclick, while others do not. I'm having a hard time figuring out how to target a specific menus index with React Hooks - currently onClick, it opens ALL the dropdown menus at once instead of the specific one I clicked on.
The prop toggleOpen is being passed down to a styled component based on the handleDropDownClick event handler.
Heres my component.
const NavBar = props => {
const [links, setLinks] = useState(null);
const [notFound, setNotFound] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const fetchLinks = () => {
if (props.prismicCtx) {
// We are using the function to get a document by its uid
const data = props.prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'navbar'),
]);
data.then(res => {
const navlinks = res.results[0].data.nav;
setLinks(navlinks);
});
}
return null;
};
const checkForLinks = () => {
if (props.prismicCtx) {
fetchLinks(props);
} else {
setNotFound(true);
}
};
useEffect(() => {
checkForLinks();
});
const handleDropdownClick = e => {
e.preventDefault();
setIsOpen(!isOpen);
};
if (links) {
const linkname = links.map(item => {
// Check to see if NavItem contains Dropdown Children
return item.items.length > 1 ? (
<Fragment>
<StyledNavBar.NavLink onClick={handleDropdownClick} href={item.primary.link.url}>
{item.primary.label[0].text}
</StyledNavBar.NavLink>
<Dropdown toggleOpen={isOpen}>
{item.items.map(subitem => {
return (
<StyledNavBar.NavLink href={subitem.sub_nav_link.url}>
<span>{subitem.sub_nav_link_label[0].text}</span>
</StyledNavBar.NavLink>
);
})}
</Dropdown>
</Fragment>
) : (
<StyledNavBar.NavLink href={item.primary.link.url}>
{item.primary.label[0].text}
</StyledNavBar.NavLink>
);
});
// Render
return (
<StyledNavBar>
<StyledNavBar.NavContainer wide>
<StyledNavBar.NavWrapper row center>
<Logo />
{linkname}
</StyledNavBar.NavWrapper>
</StyledNavBar.NavContainer>
</StyledNavBar>
);
}
if (notFound) {
return <NotFound />;
}
return <h2>Loading Nav</h2>;
};
export default NavBar;
Your problem is that your state only handles a boolean (is open or not), but you actually need multiple booleans (one "is open or not" for each menu item). You could try something like this:
const [isOpen, setIsOpen] = useState({});
const handleDropdownClick = e => {
e.preventDefault();
const currentID = e.currentTarget.id;
const newIsOpenState = isOpen[id] = !isOpen[id];
setIsOpen(newIsOpenState);
};
And finally in your HTML:
const linkname = links.map((item, index) => {
// Check to see if NavItem contains Dropdown Children
return item.items.length > 1 ? (
<Fragment>
<StyledNavBar.NavLink id={index} onClick={handleDropdownClick} href={item.primary.link.url}>
{item.primary.label[0].text}
</StyledNavBar.NavLink>
<Dropdown toggleOpen={isOpen[index]}>
// ... rest of your component
Note the new index variable in the .map function, which is used to identify which menu item you are clicking.
UPDATE:
One point that I was missing was the initialization, as mention in the other answer by #MattYao. Inside your load data, do this:
data.then(res => {
const navlinks = res.results[0].data.nav;
setLinks(navlinks);
setIsOpen(navlinks.map((link, index) => {index: false}));
});
Not related to your question, but you may want to consider skipping effects and including a key to your .map
I can see the first two useState hooks are working as expected. The problem is your 3rd useState() hook.
The issue is pretty obvious that you are referring the same state variable isOpen by a list of elements so they all have the same state. To fix the problems, I suggest the following way:
Instead of having one value of isOpen, you will need to initialise the state with an array or Map so you can refer each individual one:
const initialOpenState = [] // or using ES6 Map - new Map([]);
In your fetchLink function callback, initialise your isOpen state array values to be false. So you can put it here:
data.then(res => {
const navlinks = res.results[0].data.nav;
setLinks(navlinks);
// init your isOpen state here
navlinks.forEach(link => isOpen.push({ linkId: link.id, value: false })) //I suppose you can get an id or similar identifers
});
In your handleClick function, you have to target the link object and set it to true, instead of setting everything to true. You might need to use .find() to locate the link you are clicking:
handleClick = e => {
const currentOpenState = state;
const clickedLink = e.target.value // use your own identifier
currentOpenState[clickedLink].value = !currentOpenState[clickedLink].value;
setIsOpen(currentOpenState);
}
Update your component so the correct isOpen state is used:
<Dropdown toggleOpen={isOpen[item].value}> // replace this value
{item.items.map(subitem => {
return (
<StyledNavBar.NavLink href={subitem.sub_nav_link.url}>
<span>{subitem.sub_nav_link_label[0].text}</span>
</StyledNavBar.NavLink>
);
})}
</Dropdown>
The above code may not work for you if you just copy & paste. But it should give you an idea how things should work together.

Resources