Resizing components using .map() and inline event handlers - reactjs

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?

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.

Related

React-spring transition with non-transitioning parent element

When using react-spring it is possible to animate items as they are added / removed from an array. Now, I have an array with a fixed length where all items are null by default. When a value changes to an object, I fade in the component.
This works fine except when changing the value back to null. When unmounting, react-spring duplicates the component untill the animation of the original component is finished.
Is there a way to keep this duplicating from happening?
I made an example at https://codepen.io/AndriesVDW/pen/OJErMjv
<div id="app"></div>
.tiles {
display: flex;
gap: 10px;
list-style: none;
margin: 10px;
padding: 0
}
.tile {
height: 100px;
width: 100px;
border: 1px solid black;
border-radius: 10px;
}
.card {
height: 100%;
width: 100%;
background: red;
border-radius: 10px;
}
const { useState } = React;
const { createRoot } = ReactDOM;
const { animated, useTransition } = ReactSpring;
const App = () => {
return (
<div>
<Grid />
</div>
);
};
const Grid = () => {
const [items, setItems] = React.useState([null, null, null]);
const transition = useTransition(items, {
from: { opacity: 0, transform: "translateY(-50px)" },
enter: { opacity: 1, transform: "translateY(0px)" },
leave: { opacity: 0, transform: "translateY(-50px)" }
});
const add = () => {
setItems(["Card 1", null, null]);
};
const remove = () => {
setItems([null, null, null]);
};
return (
<div>
<button onClick={ add }>Add card to first slot</button>
<button onClick={ remove }>Remove card in first slot</button>
<ul className="tiles">
{ transition((style, item) => (
<li className="tile">
{ item && (
<animated.div className="card" style={ style }>{ item }</animated.div>
) }
</li>
)) }
</ul>
</div>
);
};
const container = document.getElementById('app');
const root = createRoot(container);
root.render(<App />);

How do I dynamically add a div where mouse was clicked in React?

I'm new to react and wonder how to do weird code stuff. I have a div component that I need to add child divs to depending on where I clicked on the div. I could do this easily in vanilla JS - here is a code sandbox of JS of what I want to do : https://codepen.io/Webasics/pen/YXXyEO
here is what I have in react so far (this is inside my App component):
const imgAdder = (e) => {
console.log(e.pageX, e.pageY)
}
<main onClick={imgAdder} </main>
$(document).ready(function() {
$(this).click(function(e) {
var x = e.pageX;
var y = e.pageY;
$('<div/>').css({
'top': y,
'left': x
}).appendTo('body');
});
});
div {
background-color: red;
width: 50px;
height: 50px;
position: absolute;
transform: translate(-50%, -50%);
/* optional */
border: 1px solid black;
/* optional */
}
h2 {
z-index: 10;
/* optional */
/* This always keeps the title on top*/
position: absolute;
}
body {
background-color: #E1E7E8;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h2>Click anywhere</h2>
Any directions would be lovely ! thank you.
function App() {
// declare array of boxes
const [boxes, setBoxes] = useState([]);
const handleClick = ({ pageX, pageY }) => {
// on every click push a new coordinate to the boxes array
setBoxes((boxes) => [...boxes, { x: pageX, y: pageY }]);
};
return (
<div className="app" onClick={handleClick}>
// display boxes
{boxes.map((box) => (
// map coordinates to left and top
<div className="box" style={{ left: box.x, top: box.y }}></div>
))}
</div>
);
}
Styles, mostly copied from the codepen
.app {
width: 100%;
height: 100vh;
}
.box {
position: absolute;
width: 50px;
height: 50px;
background: red;
transform: translate(-50%, -50%);
}
sandbox
Weird, but I like it!
https://codesandbox.io/s/elated-meadow-zuerrg?file=/src/App.js
I would simply use useEffect to register a click handler on the document and on click, add elements to a state array.
Finally, render those elements onto the page.
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const elements = useDynamicElements();
return (
<>
<h2>Click anywhere</h2>
{elements}
</>
);
}
const useDynamicElements = () => {
const [state, setState] = useState([]);
useEffect(() => {
const handler = (event) => {
setState((previous) => [
...previous,
<div style={{ top: event.pageY, left: event.pageX }} />
]);
};
document.addEventListener("click", handler);
return () => document.removeEventListener("click", handler);
});
return state;
};
An over simplified example in React could be like this:
This version can run in the snippets below for convenience.
const App = () => {
const [boxList, setBoxList] = React.useState([]);
const handleClick = (e) => {
if (e.target.classList.contains("btn")) {
setBoxList([]);
return;
}
setBoxList((prev) => {
const { pageX, pageY } = e;
const newBox = { left: pageX, top: pageY };
return [...prev, newBox];
});
};
return (
<div className="app" onClick={handleClick}>
<button className="btn">CLEAN UP</button>
<h2>Click anywhere</h2>
{boxList.length > 0 &&
boxList.map((box, index) => (
<div className="box" style={{ top: box.top, left: box.left }} key={index}></div>
))}
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#root"));
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.app {
width: 100%;
height: 100vh;
background-color: pink;
position: relative;
}
.box {
background-color: #000;
width: 50px;
height: 50px;
position: absolute;
transform: translate(-50%, -50%);
border: 1px solid black;
}
h2 {
top: 50%;
left: 50%;
position: absolute;
transform: translate(-50%, -50%);
}
.btn {
margin: 15px;
padding: 15px;
background-color: #fff;
border: 0;
border-radius: 12px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>

How can I show the uploaded image in React?

I'm going to upload an image with react now. When I selected the image and received the value of the selected image in state, I got an array like below. But how do I approach this array to see this selected image? If you look at the picture, there is no path to the picture in the array, so I want to know which value to choose to see the picture. I'd appreciate it if you let me know, thanks.
import React from 'react'
import { useState } from 'react';
import styled from 'styled-components';
function MakeModal() {
const fileInput = React.useRef(null);
const [isfile,setIsfile] = useState("");
const handleButtonClick = e => {
fileInput.current.click();
};
const handleChange = e => {
setIsfile(e.target.files[0]);
console.log(e.target.files[0]);
};
const objectUrl = URL.createObjectURL(isfile);
console.log('check',objectUrl)
return (
<Modal>
<div>
<div>
<div>
<div>
<input
type="file"
style={{display:'none'}}
ref={fileInput}
onChange={handleChange}
multiple={true}/>
<button
className='box9_2_3_3_2'
onClick={handleButtonClick}>
choose img
</button>
</div>
</div>
</div>
</div>
</Modal>
)
}
export default MakeModal;
const Modal = styled.div`
display: ${props=>props.isModal===true?'block':'none'};
position: fixed;
background-color: rgba(0,0,0,0.65);
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 500343;
.box9_2_3_3_2 {
background-color: rgb(0,149,246)!important;
border: 1px solid transparent;
border-radius: 4px;
color: white;
position: relative;
-webkit-appearance: none;
background: none;
box-sizing: border-box;
cursor: pointer;
display: block;
}
`
You only need to create an ObjectUrl from your file like this :
const objectUrl = URL.createObjectURL(file); // or file[0] if it's an array
...
<img src={objectUrl} ... />
...
A good practise is to revoke the URL when the compenent unmount, or when the file change. One exemple is to do it like this, it depends on your logic :
const [isFile, setIsFile] = useState('');
const [preview, setPreview] = useState('');
useEffect(() => {
if (isFile) {
const objectUrl = URL.createObjectURL(isFile); // create here
setPreview(objectUrl);
}
return () => URL.revokeObjectURL(isFile); // And revoke on unmount
}, [isFile]); // or whatever you called the variable you put the file in
return (
...
<img src={preview} ... />
...
)

React with TS can't see any contect inside background wrapper component

Here is the register component code -
const RegisterView = () => {
return (
<Background>
<h1>ddd</h1>
</Background>
);
};
The background scss -
.mainContainer {
display: flex;
flex-direction: row;
min-height: 80vh;
min-width: 140vh;
align-items: center;
justify-content: center;
background-color: map-get($colors, primary);
padding: 100px;
position: relative;
z-index: 10;
&:after {
background-color: #fff;
border-radius: 15px;
content: '';
position: absolute;
top: 90px;
left: 140px;
right: 140px;
bottom: 90px;
z-index: -1;
}
}
Background component view.tsx -
import classes from './Background.module.scss';
const BackgroundView = () => {
return (
<div className={classes['mainContainer']}></div>
);
};
BackgroundView.displayName = 'Background';
BackgroundView.defaultProps = {};
export default BackgroundView;
Background component tsx -
import BackgroundView from './Background.view';
const Background = () => {
return (
<BackgroundView />
);
};
Background.displayName = 'Background';
Background.defaultProps = {};
export default Background;
The Background component should be a wrapper for all other components. Here is what I'm getting right now -
I can't get any content inside the component, what is the problem?
You've wrapped BackgroundView in a Background component you've written like this:
const Background = () => {
return (
<BackgroundView />
);
};
That throws away any children you've provided to the Background component. If you want to put those children in the BackgroundView, you have to do that explicitly:
const Background = ({children}) => {
return (
<BackgroundView>{children}</BackgroundView>
);
};
You have the same problem with BackgroundView, so:
const BackgroundView = ({children}) => {
return (
<div className={classes['mainContainer']}>{children}</div>
);
};
It's also not clear why you have Background at all. You could just use BackgroundView directly.
Just as a side note, there's no need for the () around JSX elements (provided you start the JSX element on the line with the return, not the next line):
const Background = ({children}) => {
return <BackgroundView>{children}</BackgroundView>;
};
and in fact, if you like, you can use the concise form of the arrow function:
const Background = ({children}) => <BackgroundView>{children}</BackgroundView>;
but those are both just style things, you may prefer to do it differently.

Adding A button before the previousLabel Props and after the nextLabel Props in React-Paginate Not Working

Please I needed to add a button before the previousLabel property details and after the nextLabel prop details in React-Paginate but what I have below is not working. I have also tried to read the documentation and check all the prop details but still couldn't achieve the result below.
I want a first button to come before one button and a last button to come after the next button.
what the code below looks like above.
what I want to achieve below
```
import React, { useState, useEffect } from 'react'
import axios from 'axios'
import ReactPaginate from 'react-paginate';
import './styles.css'
export default function App() {
const [postsPerPage] = useState(5);
const [offset, setOffset] = useState(1);
const [posts, setAllPosts] = useState([]);
const [pageCount, setPageCount] = useState(0)
const getPostData = (data) => {
return (
data.map(post => <div className="container" key={post.id}>
<p>User ID: {post.id}</p>
<p>Title: {post.title}</p>
</div>)
)
}
const getAllPosts = async () => {
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`)
const data = res.data;
const slice = data.slice(offset - 1 , offset - 1 + postsPerPage)
// For displaying Data
const postData = getPostData(slice)
// Using Hooks to set value
setAllPosts(postData)
setPageCount(Math.ceil(data.length / postsPerPage))
}
const handlePageClick = (event) => {
const selectedPage = event.selected;
setOffset(selectedPage + 1)
};
useEffect(() => {
getAllPosts()
}, [offset])
return (
<div className="main-app">
{/* Display all the posts */}
{posts}
{/* Using React Paginate */}
<ReactPaginate
previousLabel={"previous"}
nextLabel={"next"}
breakLabel={"..."}
breakClassName={"break-me"}
pageCount={pageCount}
onPageChange={handlePageClick}
containerClassName={"pagination"}
subContainerClassName={"pages pagination"}
activeClassName={"active"} />
</div>
);
}
```
Below is the css
.main-app {
margin: 2% 10%;
border: 1px ridge #464141;
padding: 5%;
font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
font-size: 20px;
color: #464141;
}
.container {
border-bottom: #fff 2px ridge;
}
.pagination {
margin-top: 45px;
margin-left: -40px;
display: flex;
list-style: none;
outline: none;
}
.pagination>.active>a {
background-color: #47ccde;
color: #fff;
}
.pagination>li>a {
border: 1px solid #47ccde;
padding: 5px 10px;
outline: none;
cursor: pointer;
}
.pagination>li>a, .pagination>li>span {
color: #47ccde;
background-color: azure;
}

Resources