So I have this modal that is wrapped with an entire background div. My issue is that I added a close function on the background, so no matter where I click, it will close the modal.
I would like to have the useRef only target the background div and not work if I click any of the children inside of it
Here is the code
const modalRef = useRef();
const closeModal = e => {
if (modalRef.current.contains(e.target)) {
setShowModal(false);
}
}
return (
<>
{showModal ? (
<Background onClick={closeModal} ref={modalRef}>
<animated.div style={animation}>
<ModalWrapper showModal={showModal}>
<div>hi</div>
<CloseModalButton
aria-label='Close modal'
// onClick={() => setShowModal(!showModal)}
/>
</ModalWrapper>
</animated.div>
</Background>
) : null}
</>
);
};
So right now the ref is attached to the background, but if I console.log(modalRef.current) it will show me the entire jsx with all my children divs inside, but I only want to target the outside div aka the background
So whenever I click outside of my modal it will close
Note: when I console.log(modalRef.current) this is what shows up in the console aka my entire JSX
I'm also using styled-components, so I only want to target the top div which I called Background but shows sc-bdnylx iEsAwc so I have no idea how to target it since it doesn't have any ids or classNames
I tried to add this console.log(modalRef.current.children[0]) but when I implemented it into my function, it didn't work properly. This console.log does only show the divs below my Background div, but I haven't found anything about targeting just the Background only
You don't actually need to use refs here. Just add onClick={e => e.stopPropagation()} to one of the divs inside Background, like on ModalWrapper. It'll stop the click from passing through.
Related
I have hundreds of objects rendered on the screen that are each wrapped in a <OverlayTrigger> (https://react-bootstrap.github.io/components/overlays/#overlaytrigger)
If these OverlayTriggers are displaying the same/similar text, is there a way I can just instantiate one and reuse that one, instead of rendering hundreds for each object that remain inactive until they're clicked/hovered over?
If you were able to use Overlay instead of OverlayTrigger you could do this by adding event handlers to the components that you want to trigger the overlay (e.g. onClick, onMouseOver/onMouseOut). The event handler would:
update the trigger to be the element that triggered the overlay (so the overlay is displayed alongside the element)
update the show state to ensure the overlay is displayed
The HTML would allow you to define a single Overlay element that can be used by multiple objects (in this case 4 buttons):
<div>
<Overlay target={target.current} show={show} placement="right">
<Tooltip id="button-tooltip">Simple tooltip</Tooltip>
</Overlay>
<Button onMouseOver={showOverlay} onMouseOut={showOverlay}>
Test
</Button>
<Button onMouseOver={showOverlay} onMouseOut={showOverlay}>
Test 2
</Button>
<Button onMouseOver={showOverlay} onMouseOut={showOverlay}>
Test 3
</Button>
<Button onMouseOver={showOverlay} onMouseOut={showOverlay}>
Test 4
</Button>
</div>
The show state and target ref would need to be introduced:
const [show, setShow] = useState(false); // Used to display the overlay
const target = useRef(null); // Used to store the reference to the HTML element that triggered the overlay
The showOverlay function would set the target ref to the HTML element that was clicked/hovered over, then set the flag to display the Overlay:
const showOverlay = (e) => {
target.current = e.target;
setShow(!show);
};
There's a working demo of this here.
So I tried creating a simple navbar that would have its buttons controlled by useState. However I have a problem where the button icon color wont update even though the state of the variable that controls it changes.
Now, I did some testing and and added text into the icon component (not show here) and made it so it was controlled by the same state as the color on the icon is now. And for some reason when I did that the state and the text inside the component both changed correctly. Could anyone provide an explanation on why that happens? Because to me it seems like I've misunderstood how react binds things to states and controls them.
Navigation bar component
import NavButton from "./NavButton"
import { useState } from "react";
function NavBar(){
const [buttons, setButtons] = useState([
{id:1, name:"Orders", icon:"bx:bx-dollar-circle", active:false},
{id:2, name:"Menu", icon:"ic:round-restaurant-menu", active:false},
{id:3, name:"Leave", icon:"uil:exit", active:false}
]);
const toggleButton = (id) => {
setButtons(buttons.map(button => (
button.id === id ? {...button, active:!button.active} : {...button, active:false}
)))
}
return (
<div className="h-1/6 bg-white border-b-lebo flex flex-row justify-around">
<>
{buttons.map((button) => (<NavButton button={button} key={button.id} onToggle={toggleButton}/>))}
</>
</div>
)
}
export default NavBar;
Navigation button component
import Icon from "./Icon";
function NavButton({button, onToggle}){
return (
<button onClick={() => onToggle(button.id)} className={`font-bold text-gray-500 flex flex-col items-center justify-center flex-grow w-5 hover:bg-gray-100`}>
<p className="self-center">{button.name}</p>
<Icon icon={button.icon} name={button.name} color={button.active ? "#454545" : "#8b8b8b"}/>
</button>
)
}
export default NavButton;
Icon component
function Icon({icon, color, name}) {
return (
<div>
<span color={color} className="iconify h-10 w-auto self-center" data-icon={icon}></span>
</div>
)
}
export default Icon
I solved my problem by creating 2 different Icon components.
Icon and IconDark and conditionally rendering them inside the NavButton component.
Not sure if it is the "correct" way of doing things but it got the job done.
I'm going to guess the reason why it didn't render the colors correctly earlier is because of the attribute "color" inside the component. I think JSX just took it in as another prop and did nothing with it after the first render of the element.
edit 1: nvm it definitely didn't get the job done. At least not well enough. The icon swap in the render isn't fast enough so it causes the user to see the icon swap.
edit 2: This article held the answer that I needed.
https://dev.to/abachi/how-to-change-svg-s-color-in-react-42g2
It turns out that to change an svg color with react you need to set the initial fill (or for me color) value inside the svg component to "current" and then pass the real value in from the parent element conditionally.
Long story short - Controlling SVG values is a little different to controlling text values in react.
I'm trying to detect a container size change with react-resize-observer. To do so, I have embeded a ResizeObserver in a custom component of mine.
It works great when the component is used in isolation. However, as soon as it is used in a Bootstrap modal (from react-bootstrap), ResizeObserver's onResize callback is called with null values (width === height === 0).
What am I doing wrong?
The problem comes from the way the modal is displayed: with an animation. When the modal component is first rendered, it is hidden, thus react-resize-observer reports null values.
Instant fix
Turn the animation off:
<Modal
animation={false}
>
At least, you should give this a try, just to make sure that fixes the issue.
Make react-resize-observer work while keeping the animation
The trick is to start displaying ResizeObserver and its parent component only when the modal animation has actually started, thanks to the onEntering callback:
export const MyModal = () => {
const [ showComponent, setShowComponent ] = useState(false);
return (
<Modal
onEntering={() => setShowComponent(true)}
>
{showComponent && (
<div>
<ResizeObserver onResize={(rect) => {...}}/>
...
</div>
)}
</Modal>
);
}
I've been trying to understand and write code on the Box component in material-UI. (https://material-ui.com/components/box/#box)
I've been trying to override a Button component the two ways it describes in the documentation, but I have no idea how. When I run the code segment using both methods, the button appears but no color change. Then when I try to add an extra Button underneath the clone element code segment I get an error saying 'Cannot read property 'className' of undefined'.
<Box color="primary" clone>
<Button>Click</Button>
<Button>Click</Button>
</Box>
When I add a Button component underneath in the second render props way, the first button just disappears from the DOM completely.
<Box color="secondary">
{props => <Button {...props} > Click </Button>}
<Button color="secondary">Click</Button>
</Box>
Would appreciate an explanation of how overriding underlying DOM elements work.
There are a few issues with the code you've shown in your question.
primary and secondary are not valid colors within the palette. They are valid options for the color prop of Button, but here you are trying to reference colors within the theme's palette object. For this purpose, you need primary.main and secondary.main (which is what Button uses when you specify <Button color="primary">).
Box only supports a single child when using the clone property and it only supports a single child when using the render props approach. In both of your examples you have two children.
Here is the Material-UI source code that deals with the clone option:
if (clone) {
return React.cloneElement(children, {
className: clsx(children.props.className, className),
...spread,
});
}
This is creating a new child element that combines the className generated by Box with any existing class name on the child. It gets at this existing class name via children.props.className, but when there are multiple children then children will be an array of elements and will not have a props property so you get the error:
Cannot read property 'className' of undefined
Here is the Material-UI source code that deals with the render props approach:
if (typeof children === 'function') {
return children({ className, ...spread });
}
When you have more than one child, then typeof children === 'function' will not be true and it won't use the render props approach. In this case, both children just get normal react rendering and trying to render a function doesn't render anything.
Below is a working example that fixes all of these problems by using a single Button child in the clone case and a single function child in the render props case (a function that then renders two Button elements).
import React from "react";
import Button from "#material-ui/core/Button";
import Box from "#material-ui/core/Box";
export default function App() {
return (
<>
<Box color="primary.main" clone>
<Button>Click</Button>
</Box>
<Box color="secondary.main">
{props => (
<>
<Button {...props}> Click </Button>
<Button color="secondary">Click</Button>
</>
)}
</Box>
</>
);
}
I am using material UI backdrop and want to have some some tiny form inside of it. But whenever i click on its children the backdrop closes thanks to onClick which supposed to toggle the backdrop.
Is there any way how to stop backdrop onClick from triggering from inside backdrop (it's children) ?
I have tried to use useRef for elements inside but to me it seems crazy to have multiple referencies on elements and checking them in toggle function if it supposed to hide backdrop or not. Also i don't have access to some elements inside of the backdrop;
const handleBackdropToggle = e => {
console.log(e.currentTarget === backdropRef.current);
if (e.currentTarget === backdropRef.current) {
setOpen(!open);
}
};
const handleToggle = e => {
setOpen(!open);
};
<Backdrop
className={classes.backdrop}
open={open}
onClick={handleBackdropToggle}
ref={backdropRef}
>
<div className={classes.form}>
<CommonFormCard
noBorder={true}
onSubmit={onSubmit}
onBack={handleToggle}
submitButtonText={`Odeslat`}
>
<>
<h4>Od kdy má fotograf nafotit nemovitost?</h4>
<CommonForm formData={renderFormData} onChange={onChange} />
</>
</CommonFormCard>
</div>
</Backdrop>;
Rather than toggling your backdrop via onClick on the backdrop, you can use the Click away listener.
The main issue that you are likely experiencing is that you need to stop the propagation of click events in the form. The nature of DOM events is that they propagate up to all ancestors by default. You can prevent this with:
<div className={classes.form} onClick={e => e.stopPropagation()}>
I know I'm answering late. But this solution may prove most efficient.
Don't use onClick of Backdrop component. Instead, add a child button to close the backdrop. Now you can trigger children click events without needing to worry about event propagation to the backdrop parent.