Can't get ref.current.offsetWidth - reactjs

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>
);

Related

Obtaining actual array state

I've been exploring React in practice and now I've stuck.
There is the app that contains: "FlashCardsSection" (parent component)
export const FlashCardsSection = () => {
const [cardsList, setCardsList] = useState([]);
const cardAddition = (item) => {
setCardsList(() => [item, ...cardsList]);
};
const handlerDeleteCard = (cardsList) => {
console.log(cardsList);
};
return (
<section className = "cards-section">
<div className = "addition-card-block">
<PopUpOpenBtn cardAddition={cardAddition} cardsList={cardsList}
handlerDeleteCard = {handlerDeleteCard}
/>
</div>
<CardsBlock cardsList={cardsList}/>
</section>
)
};
At the "PopUpOpenBtn" there is the "AddFlashCardBtn" component which adds the "CreatedFlashCard" component to the "cardsList" property by the "cardAddition" function. Each "CreatedFlashCard" component contains the "FlashCardRemoveBtn" component. I would like to implement the function which deletes the card where occurred click on the "FlashCardRemoveBtn" component which calls the "handlerDeleteCard" function. I need an actual array version at any click but I get: when a click at the first card - empty array, the second card - array with one element, third - array with two elements, etc.
The "PopUpOpenBtn" contains intermediate components which related with creation and addition flashcard, so I passed the properties "cardsList", "handlerDeleteCard" through all to the "FlashCardRemoveBtn".
export const FlashCardRemoveBtn = (props) => {
let {handlerDeleteCard, cardsList} = props;
return(
<button className = "remove-btn" onClick={() => {
handlerDeleteCard(cardsList);
}}>Remove
</button>
)};

React ref that depends by an element's reference does not get passed to the child components

The following code creates an object ref that's called editor, but as you see it depends by the contentDiv element that's a ref to a HTMLElement. After the editor object is created it needs to be passed to the TabularRibbon. The problem is that the editor is always null in tabular component. Even if I add a conditional contentDiv?.current, in front of this, it still remains null...
Anyone has any idea?
export const Editor = () => {
let contentDiv = useRef<HTMLDivElement>(null);
let editor = useRef<Editor>();
useEffect(() => {
let options: EditorOptions = { };
editor.current = new Editor(contentDiv.current, options);
return () => {
editor.current.dispose();
}
}, [contentDiv?.current])
return (
<div >
<TabularRibbon
editor={editor.current}
/>
<div ref={contentDiv} />
..........

get iframe elements in react using ref

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);

Programmatically focus and select value in react-select

I want to be able to programmatically focus() and select() a react-select. Clicking on Add new brand below:
should render something like this:
Here's what I have so far.
My Select component is wrapped in React.forwardRef:
const Select = React.forwardRef((props, ref) => {
return (
<Creatable ref={ref} {...props} />
)
})
so that I can style it with styled-components and still have a ref to its input, like so:
const BrandSelect = styled(Select)`...`
const Button = styled.button`...`
const MyComponent = () => {
const brandSelectRef = useRef()
const [newBrand, setNewBrand] = useState(false)
const handleAddBrand = () => {
setNewBrand(true)
console.log(brandSelectRef.current)
if (brandSelectRef && brandSelectRef.current) {
brandSelectRef.current.focus()
brandSelectRef.current.select()
}
}
return (
<BrandSelect
creatable
openMenuOnFocus
ref={brandSelectRef}
defaultInputValue={newBrand ? '...' : undefined}
// ...other required react-select props
/>
<Button onClick={handleAddBrand}>Add new brand</Button>
)
}
The problem is, though, that the above code doesn't work, i.e. react-select never gets focused. Also, the log brandSelectRef.current is undefined.
I'm clearly doing something wrong here, but I can't spot what.
I think the cause is that you have to use default value for your useRef hook
const brandSelectRef = useRef(null)

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