Reputation: 278
I'm trying to implement data-rendered resizable components using the example below (first code snippet). I tried writing it with useRef but that would require putting a hook inside a loop which is against React's documentation.
This is the snippet that contains desired functionality.
const App = () => {
const ref = React.useRef(null);
const refRight = React.useRef(null);
React.useEffect(() => {
const resizeableEle = ref.current;
const styles = window.getComputedStyle(resizeableEle);
let width = parseInt(styles.width, 10);
let x = 0;
resizeableEle.style.top = '20px';
resizeableEle.style.left = '20px';
// Right resize
const onMouseMoveRightResize = (event) => {
const dx = event.clientX - x;
x = event.clientX;
width = width + dx;
resizeableEle.style.width = `${width}px`;
};
const onMouseUpRightResize = () => {
document.removeEventListener('mousemove', onMouseMoveRightResize);
};
const onMouseDownRightResize = (event) => {
x = event.clientX;
resizeableEle.style.left = styles.left;
resizeableEle.style.right = null;
document.addEventListener('mousemove', onMouseMoveRightResize);
document.addEventListener('mouseup', onMouseUpRightResize);
};
// Add mouse down event listener
const resizerRight = refRight.current;
resizerRight.addEventListener('mousedown', onMouseDownRightResize);
return () => {
resizerRight.removeEventListener('mousedown', onMouseDownRightResize);
};
}, []);
return (
<div className="container">
<div ref={ref} className="resizeable">
<p>Hello this is text</p>
<div ref={refRight} className="resizer resizer-r"></div>
</div>
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById("root")
);
.container {
border-radius: 5px;
width: 100vw;
height: 100vh;
background: #FFBC97;
position: relative;
}
.resizeable {
position: absolute;
top: 20px;
left: 150px;
border: 2px solid #533535;
width: 100px;
height: 100px;
border-radius: 3px;
display:block;
/* justify-content: center; */
align-items: center;
min-width: 15px;
min-height: 15px;
text-overflow: ellipsis !important;
white-space: nowrap !important;
overflow: hidden !important;
}
.resizer {
position: absolute;
background: black;
}
.resizer-r {
cursor: col-resize;
height: 100%;
right: 0;
top: 0;
width: 2px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
Basically I'm trying to reproduce the same resize functionality but instead of using useRef I want to define event listeners inline like this <div onMouseMove={handleMouseMove}>
. Mainly because having hooks inside a loop is not a good practie in React.
I want it to look somewhat like the second snipped below, Is this the right approach?
const App = () => {
const data = ['item1', 'item2', 'item3', 'item4', 'item5'];
return (
<div className="App">
{data.map((x, i) => {
function handleMouseDown(e) {
console.log('trying to resize');
}
function handleMouseUp() {
console.log('handling mouseUp');
}
const onMouseMoveRightResize = (e) => {
console.log('moving...');
};
return (
<div className="item" key={data[i]}>
{x}
<div key={`i${i}`} className="resizer" onMouseMove={onMouseMoveRightResize} onMouseDown={handleMouseDown} onMouseUp={handleMouseUp}></div>
</div>
);
})}
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById("root")
);
.App {
text-align: center;
}
.item{
position: relative;
border: 1px solid black;
width: 300px;
margin: 10px auto;
border-radius: 3px;
cursor: pointer;
}
.resizer {
position: absolute;
background: black;
cursor: col-resize;
height: 100%;
right: 0;
top: 0;
width: 5px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I tried reproducing that many times. I managed to change the widths of the components but I keep failing at implementing smooth resize functionality. Can anyone help me, please?
Upvotes: 1
Views: 137
Reputation: 1094
So to make sure we're answering this correctly - first of if your intention is to render this same resizable component multiple times you can just move that logic to another component and rerender it as shown in this codesandbox.
In case you wish to do something custom for each item, for these 3 functions function handleMouseDown, handleMouseUp, onMouseMoveRightResize then we could just pass these functions to the child, and expect the child to pass in the required props and handle the change - additionally from the index we know which item got clicked, resized, released. Codesandbox link (Open the console and the logs should make it clear).
So the gist of it is you can create another component which can be rendered in the map function circumventing the requirement to not use hooks directly in map functions.
App.js
import "./styles.css";
import ResizableComponent from "./ResizableComponent";
const App = () => {
const data = ["item1", "item2", "item3", "item4", "item5"];
return (
<div className="container">
{data.map((x, index) => (
<ResizableComponent index={index} key={x} />
))}
</div>
);
};
export default App;
ResizableComponent.js
import { useEffect, useRef } from "react";
const ResizableComponent = ({
index,
handleMouseDown,
handleMouseUp,
handleMouseMoveRightResize
}) => {
const ref = useRef(null);
const refRight = useRef(null);
useEffect(() => {
const resizeableEle = ref.current;
const resizerRight = refRight.current;
if (resizeableEle && resizerRight) {
const styles = window.getComputedStyle(resizeableEle);
// let width = parseInt(styles.width, 10);
// let x = 0;
resizeableEle.style.top = `${index * 120 + 20}px`;
resizeableEle.style.left = "20px";
// Right resize
const onMouseMoveRightResize = (event) => {
// const dx = event.clientX - x;
// x = event.clientX;
// width = width + dx;
// resizeableEle.style.width = `${width}px`;
handleMouseMoveRightResize();
// pass whatever props you want to the parent component
};
const onMouseUpRightResize = () => {
handleMouseUp();
document.removeEventListener("mousemove", onMouseMoveRightResize);
};
const onMouseDownRightResize = (event) => {
handleMouseDown();
// pass wahtever props you want to the parent component
// x = event.clientX;
// resizeableEle.style.left = styles.left;
// resizeableEle.style.right = '';
document.addEventListener("mousemove", onMouseMoveRightResize);
document.addEventListener("mouseup", onMouseUpRightResize);
};
resizerRight.addEventListener("mousedown", onMouseDownRightResize);
return () => {
if (resizerRight)
resizerRight.removeEventListener("mousedown", onMouseDownRightResize);
};
}
}, [handleMouseDown, handleMouseMoveRightResize, handleMouseUp, index]);
return (
<div ref={ref} className="resizeable">
<p>Hello this is text</p>
<div ref={refRight} className="resizer resizer-r"></div>
</div>
);
};
export default ResizableComponent;
Also by uncommenting the code in the second solution you can have the default resize functionality and additionally do something else in the handleMouseDown, handleMouseUp, handleMouseMoveRightResize functions.
I will try to find a more viable solution if there's one but I think this should do it.
Upvotes: 1