I have a React-mui draggable dialog component on which I am using resizable box:
return (
<StyledDialog
open={open}
classes={{root: classes.dialog, paper: classes.paper}}
PaperComponent={PaperComponent}
aria-labelledby="draggable-dialog"
>
<ResizableBox
height={520}
width={370}
minConstraints={[300, 500]}
maxConstraints={[Infinity, Infinity]}
className={classes.resizable}
>
<DialogContent classes={{root: classes.dialogContent}} id="draggable-dialog">
<IconButton className={classes.clearIcon} aria-label="Clear" onClick={onClose}>
<ClearIcon/>
</IconButton>
<iframe
src={hjelpemiddel.url}
title={hjelpemiddel.navn}
width="100%"
height="100%">
</iframe>
</DialogContent>
</ResizableBox>
</StyledDialog>
);
I would like to resize the iframe inside the dialog along with the ResizableBox. But, it seems I can only resize the width of the ResizableBox, and not the height of the box, at least the maximum height seems to be the one that is set initially. How can I fix that, so that I can resize the height as well?
UPDATE
Codesanbox is available here.
FYI, for some reason sometimes import fail message appears, but everything works fine if you refresh the page of the codesandbox.
In your CodeSandBox, based on my testing, the events for dragging & resizing are simultaneously firing. You could use the cancel prop of react-draggable so that the dragging would not occur when the resize handle is the component being interacted with.
<Draggable
cancel={".react-resizable-handle"}
...
When you do this, the draggable element will not be updating its CSS transform: translate property anymore while resizing - for this, you can opt to control your Draggable component's position. Translate/Set X & Y as necessary to retain its position while resizing. Note that the x & y state & state setters should be residing on a common parent/ancestor among these components that you will be passing down as props.
export default function App() {
// have the source of truth for the positions on a common parent/ancestor
const [x, setX] = React.useState(0);
const [y, setY] = React.useState(0);
return (
<div className="App">
<PDFDialog x={x} y={y} setX={setX} setY={setY} />
</div>
);
}
...
class PDFDialog extends React.Component {
state = {
open: true
};
render() {
const { open } = this.state;
const { classes } = this.props;
return (
<StyledDialog
open={open}
classes={{ root: classes.dialog, paper: classes.paper }}
PaperComponent={PaperComponent}
aria-labelledby="draggable-dialog"
PaperProps={{
x: this.props.x,
y: this.props.y,
setX: this.props.setX,
setY: this.props.setY
}}
>
<ResizableBox
height={520}
width={370}
minConstraints={[300, 500]}
maxConstraints={[Infinity, Infinity]}
className={classes.resizable}
onResize={(e) => {
if (e.movementX !== 0) {
this.props.setX((prev) => prev + e.movementX);
} else if (e.movementY !== 0) {
this.props.setY((prev) => prev + e.movementY / 2);
}
}}
></ResizableBox>
...
// refactored draggable component:
<Draggable
position={{ x: x, y: y }}
cancel={".react-resizable-handle"} // <-- cancel the dragging if resize is interacted with
onDrag={(e) => {
if (e.movementX !== 0) {
setX((prev) => prev + e.movementX);
}
if (e.movementY !== 0) {
setY((prev) => prev + e.movementY);
}
}}
>
<Paper {...props} />
</Draggable>
(On my CodeSandBox, I've gotten rid of constraints such as minimum height & width to clearly show the example)
You can bind the event of resize of window, calculate the new height and width and pass it to the resizable-box of yours
object.addEventListener("resize", function() {
//Here you can write logic to apply resizing on the resizable-box
});
Obviously, react-resizable uses inline CSS to handle width and height of a box, and for your issue, I simulate this issue, pay attention to the below screenshot from Google chrome devTools:
The react-resizable-box has an important flag and it overwrites the inline height value so in the view, I have the following behavior:
Your information is not enough so I cannot say directly your issue cause or causes but it is very probable CSS overwriting is the root cause of this issue.
So, inspect the resizable-box on your project and seek to find CSS overwriting.
Update after adding re-produce the issue
Actually, based on my last answer something overwrite your height now in the re-production sandbox, I delete the iframe tag, and everything works well, you pass a height="100%" attribute to your iframe tag and it prevents the change of height. Also, you pass a minConstraints={[300, 500]} to your ResizableBox component, so it could not have a smaller height than 500px.
Related
I've implemented a drawer similar to the example shown here. For working reproductions, please follow the link above and edit the Responsive Drawer on Stackblitz or Codesandbox. All that needs to be done to see the issue is to add onClick={(e) => console.log(e.target.tagName)} to the <ListItem button>.
Everything works as expected, except if you click on the top/bottom edge of a ListItem - in that case, I'm not able to get to the value assigned to the ListItem, and it's treated like an escape/cancellation and closes the drawer. In the <ListItem> the method onClick={(e) => console.log(e.target.tagName) will correctly log SPAN if you click in the middle, but will log DIV and be unresponsive if you click on the edge.
Example of one of the list items:
<Collapse in = {isOpen} timeout = 'auto' unmountOnExit>
<List component = 'div' disablePadding>
<ListItem button key = {'Something'} value = {'Something'} sx = {{pl: 4}} onClick = {(e) => handleSelect(e)}>
<ListItemIcon><StarBorder /></ListItemIcon>
<ListItemText primary = {'Something'} />
</ListItem>
</List>
</Collapse>
Overall structure of the drawer:
<List>
<Box>
<ListItem />
<Collapse>
<ListItem />
<ListItem />
</Collapse>
</Box>
</List>
onClick:
const handleSelect = (e) =>
{
const parentTag = e.target.tagName
if (parentTag === 'DIV')
{
console.log(e.target.innerHTML)
for (let child of e.target.children)
{
if (child.tagName === 'SPAN')
{
console.log(child.innerHTML)
}
}
}
else if (parentTag === 'SPAN')
{
console.log(e.target.innerHTML)
}
}
If you were to click in the middle of a ListItem, then parentTag === 'SPAN', and the console will log Something as expected.
But if you click on the top or bottom edge, then parentTag === 'DIV', and console.log(e.target.innerHTML) will show the following:
<div class="MuiListItemIcon-root..."><svg class="MuiSvgIcon-root..."><path d="..."></path>
</svg></div><div class="MuiListItemText-root..."><span class="MuiTypography-root...">
Something
</span></div><span class="MuiTouchRipple-root..."><span class="css..."><span
class="MuiTouchRipple..."></span></span></span>
There are three <span> elements, and I need the value of the first. However, console.log(child.innerHTML) always logs the later ones:
<span class="css..."><span
class="MuiTouchRipple..."></span></span>
Is there a way to get to the actual value I need? Or a better way to handle this, maybe by making the <div> unclickable/expanding the click area of the ListItem?
We can traverse till topmost parent div and search the content span from there:
const handleSelect = (e) => {
let target = e.target;
// get to the parent
while (!target.classList.contains('MuiButtonBase-root')) {
target = target.parentElement;
}
// get the content span
target = target.querySelector('.MuiTypography-root');
// utilize the content
setContent(`Clicked on: ${selected.tagName}, content: ${target.innerHTML}`);
console.log(selected);
handleDrawerToggle();
};
Even if you click on svg path element, above code will get you to the desired span element.
demo on stackblitz.
Also, we can prevent clicks on parent div using pointer-events:none CSS rule. But this will create huge unclickable area. And the SVG icon is also clickable :/ We'll have to make a lot of changes in CSS to bring the desired span in front of/covering everything.
Old answer
If you are trying to figure out which item got clicked then you can define onclick handler like this:
<ListItem button key={text}
onClick={(e) => console.log(text) } >
OR
<ListItem button key={text}
onClick={(e) => handleSelect(text) } >
This will give you the list item name right away. Then you can open corresponding content.
That's actually a CSS problem. You need to make the child elements width and height equal to the parent elements width and height. This is true for every element which is inline by default and you want to work with it.
Here are some docs about the CSS box model:
box model
understanding the inline box model
In this case, you want to change the display element in ListItem to div
AKA
<ListItem component="div">
// some stuff
</ListItem>
I'm trying to show a tooltip when a Legend component get hover. For that I have a Father component that has an useState hook in order to pass to the Leaflet Map component the index for an array Location and change the Permanent property if these index are equal.
const permanent = i === showLocationPosition;
The showLocationPosition is the index for the location that is getting hover, getting by props for its Father component.
<Marker
position={[position[0], position[1]]}
key={index}
icon={divIcon({ className: 'marker-dot dot-hover', html: ReactDOMServer.renderToString(stringHTML), iconSize: [30, 30] })}
>
<Tooltip direction="bottom" opacity={1} offset={new Point(xPoint, 10)} permanent={permanent}>
<div className={`popup-container-header ${item.count ? 'w-80' : 'w-40 text-center'}`}>
<p className="w-full">
{type_of_country_operation ?? item.name}
</p>
{item.count && <p>{item.count}</p>}
</div>
{item.summary && <p className="popup-container-main">{item.summary}</p>}
</Tooltip>
</Marker>
I could validate that the permanent variable changes but the Tooltip does not apear.
Any advice ? Thanks!
The reason why the change in permanent doesn't help is because underlying leaflet options are treated as immutable by react-leaflet. So even as your showLocationPosition might change (which changes permanent), the Tooltip was already created and will not respond to changes in that prop.
A quick and dirty way would be to use the key prop on the tooltip also, which can be a combination of the index and the permanent status:
<Tooltip {...otherProps} key={`${index}-${permanent}`}>
This would force a rerender of that Tooltip component when the value of permanent changes.
I would consider a different approach. If you don't need to also render the Tooltip when you mouseover the Marker it originates from, just conditionally render it based on permanent:
<Marker {...markerprops}>
{permanent && <Tooltip direction="..." offset={...} permanent={permanent}>
{stuff}
</Tooltip>}
</Marker>
You may want to change the name permanent to something else, like currentOpenTooltip. Now, if you also want to have the tooltip open and close properly when the user mouses over a marker, you'll need to add a condition for that. You can use a state variable to keep track of what Marker is being moused over, and use event handlers to control that state variable.
const Father = () => {
const [currentlyMousedOverMarker, setCurrentlyMousedOverMarker] = useState(-1);
return (
<MapContainer>
{markersData.map((marker, index) => {
<Marker
{...markerprops}
eventHandlers={{
mouseenter: () => { setCurrentlyMousedOverMarker(index) },
mouseleave: () => { setCurrentlyMousedOverMarker(-1) }
}}
>
{permanent || currentlyMousedOverMarker === index &&
(
<Tooltip permanent={permanent || currentlyMousedOverMarker}>
{stuff}
</Tooltip>
)
}
</Marker>
})}
</MapContainer>
)
}
Obviously this code example is simplified and doesn't contain any of the logic you already had for permanent, but its just to give you an idea that the tooltip should only be rendered if either condition is true.
So for an app, I decided to show an equation (for an app), and have real-time value updates for each value (with a button to show/hide the equation at will). So for that, not only do I have an equation component in the main app component (App.js), I also decided to put the equation component in the same file as my values component (value.js), in order to access the values and pass them as props to the equation component. I figured that I could hide the equation component in the value component, so only the equation component in the main app component is showing, and so I can also access the props at the same time, so I typed in something like this:
<Equation
className="hide"
FLoad={this.state.FLoad}
DLoad={this.state.DLoad}
DLowerBack={this.state.DLowerBack}
FTorso={this.state.FTorso}
DTorso={this.state.DTorso}
FLowerBack={this.state.FLowerBack}/>
and the "hide" class looks something like:
.hide {
display: none;
}
However, when I run my app, both show up simultaneously, and the two equations overlap each other. Why is that? Why doesn't the equation in the values component hide, even with the proper CSS tags? I hope someone can help me out. Thanks in advance.
EDIT: Here's some more code for some more context
render() {
return(
<div className="Equation">Force of Lower Back () = (-1 x (Force Load x ( Distance Load / Distance Lower Back)) + (-1 x Force Torso x (Distance Torso / Distance Lower Back))
<div>Upward Force From Legs () = Force Load + Force Torso + Force Lower Back</div></div>
);
}
}
^The code in the equation component
EDIT #2:
{equation ? <div>
<Equation/>
<button className="EquationButton" onClick = {() => equationVisibility(!equation)}>Hide Equation</button>
</div> :
<button className="EquationButton" onClick = {() => equationVisibility(!equation)}>Show Equation</button>}
<Input />
</div>
^Here's the code in the main app component as well. (The Input component being the values component I was talking about earlier)
If I understood correctly, the className="hide" is not working on your Equation component. It can be due to this Equation not using it internally. You should receive the className in the Equation and apply it manually to the internal component that needs to be hidden.
const Equation = ({ className }) => (
<div className={className} />
)
However, I would recommend you to create a prop to hide it or not, this way:
const Equation = ({ hide }) => {
if (hide) {
return null
}
return <div>{...}</div>
}
So you can use it this way:
<Equation
hide
// other props
/>
EDIT:
Based on the new piece of code that you showed, you can do this:
render() {
return(
<div className={`Equation ${this.props.className}`}> // <--- add this
Force of Lower Back () = (-1 x (Force Load x ( Distance Load / Distance Lower Back)) + (-1 x Force Torso x (Distance Torso / Distance Lower Back))
<div>
Upward Force From Legs () = Force Load + Force Torso + Force Lower Back
</div>
</div>
);
}
When you use a custom component like that you need to pass the appropriate props. Maybe you should add a className prop to the component and then add the following line:
<element className={className} />
You can use the ... spread operator to add all of the properties:
const Equation = ({...props}) => {
return (
<element {...props} />
)
}
I came across this line of code via a snippet on https://usehooks.com,
document.querySelector('body').current
I haven't been able to find .current in the specification at all.
I was hoping someone could clarify its purpose in this context.
It's being used within the IntersectionObserver API in the full example (below) - perhaps the API is exposing the property?
Any help is much appreciated. Thanks in advance.
Following is the full source code:
import { useState, useEffect, useRef } from 'react';
// Usage
function App() {
// Ref for the element that we want to detect whether on screen
const ref = useRef();
// Call the hook passing in ref and root margin
// In this case it would only be considered onScreen if more ...
// ... than 300px of element is visible.
const onScreen = useOnScreen(ref, '-300px');
return (
<div>
<div style={{ height: '100vh' }}>
<h1>Scroll down to next section 👇</h1>
</div>
<div
ref={ref}
style={{
height: '100vh',
backgroundColor: onScreen ? '#23cebd' : '#efefef'
}}
>
{onScreen ? (
<div>
<h1>Hey I'm on the screen</h1>
<img src="https://i.giphy.com/media/ASd0Ukj0y3qMM/giphy.gif" />
</div>
) : (
<h1>Scroll down 300px from the top of this section 👇</h1>
)}
</div>
</div>
);
}
// Hook
function useOnScreen(ref, margin = '0px') {
// State and setter for storing whether element is visible
const [isIntersecting, setIntersecting] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
// Update our state when observer callback fires
setIntersecting(entry.isIntersecting);
},
{
rootMargin: margin,
root: document.querySelector('body').current
}
);
if (ref.current) {
observer.observe(ref.current);
}
return () => {
observer.unobserve(ref.current);
};
}, []); // Empty array ensures that effect is only run on mount and unmount
return isIntersecting;
}
document.querySelector('body').current is just a property of the body element, which has nothing to do with document.querySelector. It may have been set somewhere else as it is not an existing property of the body element.
var body = document.querySelector("body");
console.log("body.current:", "body.current");
body.current = "SOMEVALUE";
console.log("After setting body.current");
console.log("body.current:", "body.current");
Sorry to disappoint, but it doesn't do anything. It's just a way to supply undefined to the IntersectionObserver API. If you replace document.querySelector('body').current with undefined or remove the entire root field altogether, you still get the same result.
I removed that field to test it to verify the same behavior. Try it yourself in the Codesandbox link here.
As seen by this comment on the example, it can be removed entirely:
You can remove root entirely, since it defaults to the viewport anyway (also document.querySelector('body').current is always undefined, could be document.body but isn't needed anyway)
I have an animated component that slides up/down depending on the prop (true or false). I'm using maxHeight: 0 to hide the component (transition is being done with CSS) and that's the default state that's being passed as prop. For the opened style I use a maxHeight much bigger than needed just to make sure the content will fit properly. After it's opened I'm able to get its height by ref and set the maxHeight accordingly.
export default class AnimatedInput extends React.Component {
constructor (props) {
super(props);
this.state = {
height: 600
}
}
componentDidUpdate(prevProps) {
var height = this.refs.inputNode ? this.refs.inputNode.clientHeight : height;
console.log(height);
if (this.props.open === false && prevProps.open === true) {
this.setState({height: height});
}
}
render () {
var {height} = this.state;
let test = this.props.open ? 'boxVisible' : 'boxHidden';
var styles = {
boxHidden: {
...
maxHeight: 0,
},
boxVisible: {
....
maxHeight: height,
}
}
return (
<div style={styles[test]} ref="inputNode">
{this.props.children}
</div>
)
}
}
There are 2 problems with this approach:
The first time it's opened and closed is not smooth due to maxHeight being larger than it should (maybe render the opened one off-screen and get its height first?)
If it's closed before fully opened the height will be lower than it should (I suppose it's an easy fix - just need to stop updating the height value).
Am I on the right track? How would you fix these? Should I stick to CSS or maybe make the transition entirely in JS. Thanks for your suggestions!
You're looking for ReactCSSTransitionGroup. I used this for the exact same thing you are.