I have a situation when SVG loaded from the server after the page render and after that, I need to add certain events to its elements. Let's say after clicking on a rect it shows an alert.
import React, { useEffect, useState } from 'react';
import css from "./room-plan.module.css";
import { Button } from "../../components";
export default function RoomPlan({ svg }) {
useEffect(() => {
var elements = Array.from(document.querySelectorAll('svg rect'));
elements.forEach(function(el) {
el.addEventListener("click", alert("hello"));
})
}, [])
return (
<div>
<h2 className={css.labels}>Select desk</h2>
<div id={css.roomPlan} dangerouslySetInnerHTML={{__html: svg, }}></div>
<div className={css.bookingButtons}>
<Button>CANCEL</Button>
<Button>BOOK DESK</Button>
</div>
</div>
);
}
But it totally does not work. Just does not add any events on elements. Cuz when it looks for the required selector it does not exist yet.
The answer above is correct, but make sure to include a return function inside the useEffect for cleanup during component unmount
useEffect(() => {
var elements = Array.from(document.querySelectorAll('svg rect'));
const listener = () => alert("hello");
if(elements) {
elements.forEach(function(el) {
el.addEventListener("click", listener);
})
}
return function cleanup(){
if(elements) {
elements.forEach(function(el) {
el.removeEventListener("click", listener);
})
}
}
},[])
Related
I have a navbar with position sticky and when I go up to top 0 I change the color, using useRef, I apply a class .ToolbarSticky when the getBoundingClientRect().top is 0, that is, it is up, although it works it gives me an error for undefined as seen in the console
this is my code
import { memo, useRef } from 'react';
import styles from '../styles/NotesToolbar.module.css';
import FilterSelect from './FilterSelect';
import NotesCounter from './NotesCounter';
const NotesToolbar = () => {
const toolbarRef = useRef();
window.addEventListener('scroll', () => {
if (toolbarRef.current.getBoundingClientRect().top <= 0) {
toolbarRef.current.classList.add(styles.ToolbarSticky);
} else {
toolbarRef.current.classList.remove(styles.ToolbarSticky);
}
});
return (
<div className={styles.Toolbar} ref={toolbarRef}>
<div className={styles.ToolbarLeft}>
<FilterSelect />
</div>
<div className={styles.ToolbarRight}>
<NotesCounter />
</div>
</div>
);
};
export default memo(NotesToolbar);
Few things about your code
First - you should have the event listener inside a useEffect and cleanup after the component unrenders or you will have tons of eventListeners
Nest - ref is defined (or assigned) to the dom element just before render. So ref will be undefined at first. A simple check if(ref.current) do_stuff will fix it.
useEffect(() => {
window.addEventListener('scroll', scrollHandler);
return(() => window.removeEventListener('scroll', scrollHandler);
}, [])
You can define the scrollHandler function inside or outside the effect (but if function is heavy, outside would be better)
const scrollHandler = () => {
if (toolbarRef.current) {
if (toolbarRef.current.getBoundingClientRect().top <= 0) {
toolbarRef.current.classList.add(styles.ToolbarSticky);
} else {
toolbarRef.current.classList.remove(styles.ToolbarSticky);
}
}
Seen similar issues here, but couldn't wrap my mind on how this works. New to functional components and React overall.
Parent contains the Child, which is a modal. Parent has a div that triggers showing the Child modal, and the Child modal has a close button that triggers its hiding. When I click on the div component in Parent, I need to show and hide the Child modal. When I click on the close button in the Child, I need to hide the Child component.
The Parent component:
import React, { useState } from "react";
import Child from "./Child";
const Parent = () => {
const [buttonState, setbuttonState] = useState({
buttonState: false,
});
const onParentClick = (e) => {
e.preventDefault();
setbuttonState(!buttonState);
};
return (
<div>
<div onClick={onParentClick}></div>
<Child isOpen={buttonState} onParentClick={onParentClick} />
</div>
);
};
export default Parent;
The Child component:
import React, { useState } from "react";
const Child = (props) => {
const [buttonState, setButtonState] = useState({
buttonState: props.isOpen,
});
const onChildClick = (e) => {
e.preventDefault();
setButtonState(false);
props.onParentClick();
};
return (
<div
className={
buttonState ? "child-modal-opened" : "child-modal-closed"
}
>
<div onClick={onChildClick}>Close</div>
</div>
);
};
export default Child;
For some reason, can't make this work. What am I missing here?
Looks like useState() is used incorrectly.
const [buttonState, setbuttonState] = useState({
buttonState: false,
});
results in buttonState being { buttonState: false}, so setbuttonState(!buttonState) does not work as intended.
Here's updated Parent component with useState(false) instead (setting initial buttonState value to false)
import React, { useState } from "react";
import Child from "./Child";
const Parent = () => {
const [buttonState, setbuttonState] = useState(false);
const onParentClick = (e) => {
e.preventDefault();
setbuttonState(!buttonState);
};
return (
<div>
<div onClick={onParentClick}></div>
<Child isOpen={buttonState} onParentClick={onParentClick} />
</div>
);
};
export default Parent;
P.S.
As #Will suggested, there is no need to create another state in Child, it can be passed from Parent
import React, { useState } from "react";
const Child = (props) => {
return (
<div
className={
props.isOpen ? "child-modal-opened" : "child-modal-closed"
}
>
<div onClick={props.onParentClick}>Close</div>
</div>
);
};
It looks like onParentClick is defined so as to take an event object as a parameter and call preventDefault() on that, but you're calling it without any arguments. Does it work like this: props.onParentClick(e);?
I am trying to detect a key press anywhere on the page in an app built with React.
I remember it being quite easy in jQuery, but I wanted to see if it could be done in React. I didn't want to make a 'frankenstein' project i.e. have some parts in react and some parts in jQuery, if at all avoidable.
So far I have:
export default function App() {
const handleKeyPress = (event) => {
console.log(event.keyCode);
}
return (
<div
onKeyDown={handleKeyPress}
tabIndex="0"
>
<Header page_num={100}/>
</div>
);
}
But it only seems to work when a user clicks on the actual div in the page, then presses a key.
I want the key press to be detected without the user having to click anywhere on the page.
You can use useEffect hook to achieve this and adding an event listener to document:
import React, { useEffect } from "react";
export default ({ name }) => {
useEffect(() => {
function handleKeyDown(e) {
console.log(e.keyCode);
}
document.addEventListener('keydown', handleKeyDown);
// Don't forget to clean up
return function cleanup() {
document.removeEventListener('keydown', handleKeyDown);
}
}, []);
return <div>Keydown</div>;
};
Here is an example in action.
Assuming the <div> is focused via tabindex or similar, you'd be able to see see in the keydown handler that e.target would be the <div>. You can also wrap the functionality in another component and in it's keydown handler check if the keydown was executed on that element instead of outside using contains.
Here is another example that uses contains to check if the event target is within the div ref:
import React, { useEffect, useRef } from "react";
function Hello({ onKeyDown }) {
const ref = useRef();
useEffect(() => {
function handleKeyDown(e) {
// check if keydown was contained in target div
if (!ref.current || ref.current.contains(event.target)) {
return;
}
// Emit event to parent component
onKeyDown(e);
}
document.addEventListener("keydown", handleKeyDown);
return function cleanup() {
document.removeEventListener("keydown", handleKeyDown);
};
}, []);
return (
<div ref={ref} tabIndex="0">
foo bar baz
</div>
);
}
export default Hello;
Hopefully that helps!
Where's the right place to put code that interacts with the DOM in a gatsby site? I want to toggle the visibility of some components by adding/removing a class when another element is clicked.
The gatsby-browser.js file seems like it should contain this code but the API doesn't seem to call any of the functions after the DOM has loaded.
Similarly, using Helmet calls it too soon. Using window.onload never seems to trigger at all regardless of where it's included.
window.onload = function () {
// add event listener to the toggle control
}
Is there an event I can use to run my code when the DOM is ready?
Do you really need to wait for the DOM to be ready? When working in react you need to change the way you think about these things. For example you could add an on click that changes state and then reflect the state change in your classname prop.
Code Example:
import React, { useState } from "react"
const MyApp = () => {
const [visible, setVisible] = useState(true) // true is the initial state
return (
<div>
<div className={visible ? "visible-class" : "hidden-class"}>
My content
</div>
<button onClick={() => setVisible(!visible)}>Click me!</button>
</div>
)
}
export default MyApp
Or you could take it a step further and not even render that content to the DOM until you want to.
Example:
import React, { useState } from "react"
const MyApp = () => {
const [visible, setVisible] = useState(true) // true is the inital state
return (
<div>
<button onClick={() => setVisible(!visible)}>Click me!</button>
{visible && <div>My content here</div>}
</div>
)
}
export default MyApp
You can use the React cyclelife with componentDidMount().
This need to update your component like that :
import React from 'react'
class YourComponent extends React.Component {
componentDidMount() {
// Your Javascript function here
}
render() {
return(
<div className="YourComponentHere"></div>
)
}
}
export default YourComponent
Hope that help you!
If your component is a functional component, try using React Hook useEffect, which will guarantee the execution after the component is rendered.
import React, { useEffect } from 'react'
const MyComponent = () => {
useEffect(() => {
console.log("Document loaded");
});
return (
<main>
<text>Pretty much the component's body code around here</text>
</main>
)
}
export default MyComponent
I'm trying to close a popover that can only be closed via a close callback in its render props. I was wondering how I can use hooks or some other strategy to save this callback function between renders to call it in a useEffect. I tried using useContext to no avail: https://codesandbox.io/s/popover-close-from-content-y637f
You could use a ref to save the close function for use in useEffect. Here is a working codesandbox: https://codesandbox.io/s/popover-close-from-content-sgmgs
import React, { useEffect, useState, useRef } from "react";
import { Block } from "baseui/block";
import { Button } from "baseui/button";
import { StatefulPopover } from "baseui/popover";
export default () => {
const closeRef = useRef();
const [state, setState] = useState({ isSaving: false });
useEffect(() => {
if (state.isSaving) {
const timeout = setTimeout(() => {
console.log("closing", closeRef.current);
// close popover from here
closeRef.current && closeRef.current();
setState({ isSaving: false });
}, 5000);
return () => {
clearTimeout(timeout);
};
}
}, [state.isSaving]);
const onSave = () => {
console.log("save btn clicked");
setState({ isSaving: true });
};
return (
<div>
<StatefulPopover
content={(
{ close } // I need to call close per this library's API to close
) => {
closeRef.current = close;
return (
<Block padding="scale500" maxWidth="300px">
<Block paddingBottom="scale400">
content render prop is passed a <code>close()</code> callback so
it you can manually trigger popover close from within
</Block>
<Button isLoading={state.isSaving} onClick={onSave}>
Save
</Button>
</Block>
);
}}
>
<Button>Click Me</Button>
</StatefulPopover>
</div>
);
};
Context isn't working as you expect because you are attempting to access the context using useContext outside of the Provider. In order to access the value from the Provider, the useContext hooks must be utilized inside of a child component of the context Provider. Otherwise, the useContext will just get the default value passed into createContext.