I have created an app which will have a button in the start, and on click, it will create a parent div for existing div. Child div should be draggable inside its parent div. When I try to drag child component its parent component are also getting dragged.
Expected solution - video
My code -
import React, { useState } from 'react';
import Draggable from 'react-draggable';
function ParentDraggable(props) {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const handleMouseDown = (e) => {
let startX = e.clientX;
let startY = e.clientY;
const handleMouseMove = (e) => {
setX(x + e.clientX - startX);
setY(y + e.clientY - startY);
startX = e.clientX;
startY = e.clientY;
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', () => {
document.removeEventListener('mousemove', handleMouseMove);
});
};
return (
<Draggable bounds='parent' handle='.handle' onDrag={e=>e.stopPropagation()}>
<div
style={{
position: 'relative',
top: y,
left: x,
border: '1px solid black',
width: `${props.w}px`,
height: `${props.h}px`,
}}
>
<div
style={{
height: 20,
backgroundColor: 'gray',
}}
onMouseDown={handleMouseDown}
className='handle'
>
Title Bar
</div>
{props.children}
</div>
</Draggable>
);
}
function App() {
const [parents, setParents] = useState([]);
const [w, setW] = useState(200)
const [h, setH] = useState(200)
const handleAddParent = () => {
setParents([ <ParentDraggable w = {w} h={h}>
{parents}
</ParentDraggable>]);
setW(prev => prev + 100)
setH(prev => prev + 100)
};
return (
<>
<button onClick={handleAddParent}>AddParent</button>
<div style={{height: '97vh'}}>
{parents}
</div>
</>
);
}
export default App;
Please help me achieve my goal.
Your additions to Draggable doesn't seem to really do anything. Instead of DIY, what you are missing is nodeRef(). The React-draggable docs are not the easiest to read, but there's extra something there if you are interested.
Try this out, you'll want to use something like useId() to target the handle and you really need a combo if useRef() and nodeRef(). You will need to make your "parents" stack again, I hardcoded this one for simplicity.
import React, { useRef } from "react"
import Draggable from "react-draggable"
function App() {
const r1 = useRef()
const r2 = useRef()
return (
<div style={{ height: "97vh" }}>
<Draggable bounds="parent" handle=".handlea" nodeRef={r1.current}>
<div
ref={r1}
style={{
border: "1px solid black",
width: `300px`,
height: `300px`,
}}
>
<div
style={{
height: 20,
backgroundColor: "gray",
}}
className="handlea"
>
Title Bar
</div>
<Draggable bounds="parent" handle=".handleb" nodeRef={r2.current}>
<div
ref={r2}
style={{
border: "1px solid black",
width: `100px`,
height: `100px`,
}}
>
<div
style={{
height: 20,
backgroundColor: "gray",
}}
className="handleb"
>
Title Bar
</div>
</div>
</Draggable>
</div>
</Draggable>
</div>
)
}
export default App
Related
I have the following component:
import * as React from "react";
import { createTheme, ThemeProvider } from '#mui/material/styles'
import { Box } from '#mui/system';
import { InputBase, TextField, Typography } from "#mui/material";
import ReactQuill from 'react-quill';
import { NoEncryption } from "#mui/icons-material";
type Props = {
issueId: string
}
export default function DialogBox({ issueId }: Props) {
const myTheme = createTheme({
// Set up your custom MUI theme here
})
const [newMsg, setNewMsg] = React.useState("");
const [startNewMsg, setStartNewMsg] = React.useState(false)
const handleNewMsgInput = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
}
const handleKeyPress = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "Escape") {
// setStartNewMsg(false);
setNewMsg((prev) => "");
}
}
return (
<Box flexDirection="column" sx={{ display: "flex", alignItems: "center", backgroundColor: "lightblue", height: "100%", gap: "2rem" }} onKeyPress={(e) => {
handleKeyPress(e)
}}>
<Typography
sx={{
width: "fit-content",
height: "fit-content",
fontFamily: "Amatic SC",
background: "lightblue",
fontSize: "3rem"
}}>
Message board
</Typography>
{startNewMsg ?
<Box sx={{ width: "fit-content", height: "fit-content" }}>
<ReactQuill style={{ backgroundColor: "white", height: "10rem", maxWidth: "30rem", maxHeight: "10rem" }} theme="snow" />
</Box>
:
<TextField id="filled-basic" label="write new message" sx={{ "& fieldset": { border: 'none' }, backgroundColor: "white", borderRadius: "5px" }} variant="filled" fullWidth={true} onClick={(e) => setStartNewMsg((prev) => true)} onChange={(e) => handleNewMsgInput(e)} />}
</Box >
)
}
Which's causing me the following issue of text appearing out of my white textbox:
I notice on inspection the following property which's responsible for the problem:
My question is, what would be the best way to manipulating the values of that element? How should I retrieve them?
Regards!
Use ch units (the size of the text characters) and adjust the size based on the input.
Example:
const Test = () => {
const [text, setText] = useState('');
return (
<div>
<input
value={text}
onChange= {(e) => setText(e.target.value)}
style= {{height: `${text.length}ch`, width: `${text.length}ch`}}
/>
</div>
);
};
^ that is an input box that will grow based on the size of the text.
You don't want:
height: 100%;
as this will only allow the height to be as big as its parent container.
I am working on a React Native Project, I want to move the label along with the slider value. I tried many ways but its not working accurately.
Here is my code:
import React, { useState } from 'react'
import { Text, StyleSheet, View, Dimensions } from 'react-native'
import Slider from '#react-native-community/slider';
function LabelSlider() {
const windowWidth = Dimensions.get('window').width;
const [value, setValue] = useState(0)
// const left = value * (windowWidth-60)/85;
const [showValue, setShowValue] = useState(false)
//var percent = (percentToGet / 100) * number;
return (
<View>
{showValue ?
<View >
<Text style={{ width:50, textAlign: 'auto', left: value}}>{value}</Text>
</View> :
<></>}
<Slider
style={{ width: 200, height: 40 }}
maximumValue={300}
minimumValue={0}
step={1}
value={value}
onValueChange={(value) => setValue(value)}
onSlidingStart={() => setShowValue(true)}
onSlidingComplete={() => setShowValue(true)}
minimumTrackTintColor="#FFFFFF"
maximumTrackTintColor="#000000"
/>
</View>
)
}
export { LabelSlider }
I have also attached two images of the output I was able to get:
Image 1,
Image 2
You can use a shared value and interpolate the left position for the label.
Right now what happens is, that the left value goes from 0 to 300. This makes the label unresponsive. You can do something as shown below.
Also, here's a Snack for the implementation.
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Slider from '#react-native-community/slider';
import Animated, {
useSharedValue,
useAnimatedStyle,
interpolate,
Extrapolate,
} from 'react-native-reanimated';
// Constants
const MAX_SLIDER_VALUE = 300;
const MIN_SLIDER_VALUE = 0;
const SLIDER_WIDTH = 200;
export default function App() {
// State to store progress value of slider
const [value, setValue] = React.useState(0);
// Shared value for storing label position
const progress = useSharedValue(0);
// Whenever `value` changes, update the progress shared value
// This will accordingly update the left position of the label
React.useEffect(() => {
progress.value = value;
}, [value]);
// Animate the left position according to the shared value
// Also Extrapolate it so that it doesn't exceed 100%
const animatedStyle = useAnimatedStyle(() => {
const leftPosition = interpolate(
progress.value,
[MIN_SLIDER_VALUE, MAX_SLIDER_VALUE],
[0, 100],
Extrapolate.CLAMP
);
return {
left: `${leftPosition}%`,
};
});
return (
<View style={styles.container}>
<View style={{ width: SLIDER_WIDTH }}>
<Animated.View style={[styles.labelView, animatedStyle]}>
<Text>{value}</Text>
</Animated.View>
<Slider
style={{ width: SLIDER_WIDTH, height: 40 }}
maximumValue={MAX_SLIDER_VALUE}
minimumValue={MIN_SLIDER_VALUE}
step={1}
value={value}
onValueChange={setValue}
minimumTrackTintColor="#FFFFFF"
maximumTrackTintColor="#000000"
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#ecf0f1',
padding: 8,
},
labelView: {
marginBottom: 20,
},
});
You can use react native element slider for this, I have given the example code below
import React from 'react';
import { View } from 'react-native';
import {Slider} from 'react-native-elements';
const App = () => {
return (
<View
style={{
flex: 1,
justifyContent: "center",
}}>
<Slider
thumbStyle={{height: 15, width: 15, backgroundColor: 'orange'}}
maximumTrackTintColor="grey"
minimumTrackTintColor="orange"
thumbProps={{
children: (
<View
style={{
color: 'green',
marginTop: -22,
width: 100,
}}>
<Text>Value</Text>
</View>
),
}}
/>
</View>
)
}
export default App;
I have piece of code:
`function App() {
const myRef = useRef(null);
const onWheel = e => {
e.preventDefault();
const containerScrollPosition = myRef.current.scrollLeft;
inputEl.current.scrollTo({
top: 0,
left: containerScrollPosition + e.deltaY * 0.35,
behaviour: "smooth"
});
};
return (
<div
className="App"
style={{
height: 440,
width: "100%"
}}
onWheel={onWheel}
>
<AutoSizer>
{({ height, width }) => (
<List
ref={myRef}
height={height}
itemCount={30}
itemSize={600}
layout="horizontal"
width={width}
>
{Grid}
</List>
)}
</AutoSizer>
</div>
);
}
When i use myRef for List component, myRef.current.scrollLeft/myRef.current.clientHeight returns undefined(myRef.current returns correct component node). If i use myRef for div.App all goes right.
What could be the problem?
The List ref is not a dom node and does not have scrollLeft property. You could use the scrollOffset of the list which is stored in it's state.
const handleScrollLeft = () => {
let scrollPosition = listRef.state.scrollOffset - 50
listRef.scrollTo(scrollPosition)
}
I'm mainly a backend engineer and have been trying to implement, and failing, a simple drag and drop for a slider I am making in React.
First I will show you the behavior without using debounce:
no-debounce
And here is the behavior with debounce:
with-debounce
The debounce I took from here with a little modification.
I think I have two problems, one is fast flickering, which debounce should solve, and the other is the incorrect left which I cannot figure how to fix. For some reason, onDrag, rect.left has all the left margins of the parents (100 + 10) added to it as well. This happens on Chrome and Safari.
My question is, how do I make this drag and drop work? What am I doing wrong? My code is below:
import React, {
Dispatch,
MouseEvent,
RefObject,
SetStateAction,
useEffect,
useRef,
useState
} from "react";
const useDebounce = (callback: any, delay: number) => {
const latestCallback = useRef(callback);
const latestTimeout = useRef(1);
useEffect(() => {
latestCallback.current = callback;
}, [callback]);
return (obj: any) => {
const { event, args } = obj;
event.persist();
if (latestTimeout.current) {
clearTimeout(latestTimeout.current);
}
latestTimeout.current = window.setTimeout(
() => latestCallback.current(event, ...args),
delay
);
};
};
const setPosition = (
event: any,
setter: any
) => {
const rect = event.target.getBoundingClientRect();
const clientX: number = event.pageX;
console.log('clientX: ', clientX)
// console.log(rect.left)
setter(clientX - rect.left)
};
const Slider: React.FC = () => {
const [x, setX] = useState(null);
const handleOnDrag = useDebounce(setPosition, 100)
return (
<div style={{ position: "absolute", margin: '10px' }}>
<div
style={{ position: "absolute", left: "0", top: "0" }}
onDragOver={e => e.preventDefault()}
>
<svg width="300" height="10">
<rect
y="5"
width="300"
height="2"
rx="10"
ry="10"
style={{ fill: "rgb(96,125,139)" }}
/>
</svg>
</div>
<div
draggable={true}
onDrag={event => handleOnDrag({event, args: [setX]})}
// onDrag={event => setPosition(event, setX)}
style={{
position: "absolute",
left: (x || 0).toString() + "px",
top: "5px",
width: "10px",
height: "10px",
padding: "0px",
}}
>
<svg width="10" height="10" style={{display:"block"}}>
<circle cx="5" cy="5" r="4" />
</svg>
</div>
</div>
);
};
Thank you.
Draggable and onDrag hast its own woes. Tried my hand with simple mouse events.
You can find a working code in the following sandbox
The source for it is as follows
import React, { useRef, useState, useEffect, useCallback } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const isDragging = useRef(false);
const dragHeadRef = useRef();
const [position, setPosition] = useState(0);
const onMouseDown = useCallback(e => {
if (dragHeadRef.current && dragHeadRef.current.contains(e.target)) {
isDragging.current = true;
}
}, []);
const onMouseUp = useCallback(() => {
if (isDragging.current) {
isDragging.current = false;
}
}, []);
const onMouseMove = useCallback(e => {
if (isDragging.current) {
setPosition(position => position + e.movementX);
}
}, []);
useEffect(() => {
document.addEventListener("mouseup", onMouseUp);
document.addEventListener("mousedown", onMouseDown);
document.addEventListener("mousemove", onMouseMove);
return () => {
document.removeEventListener("mouseup", onMouseUp);
document.removeEventListener("mousedown", onMouseDown);
document.removeEventListener("mousemove", onMouseMove);
};
}, [onMouseMove, onMouseDown, onMouseUp]);
return (
<div
style={{
flex: "1",
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100vh"
}}
>
<div
style={{
height: "5px",
width: "500px",
background: "black",
position: "absolute"
}}
>
<div
ref={dragHeadRef}
style={{
left: `${position}px`,
transition: 'left 0.1s ease-out',
top: "-12.5px",
position: "relative",
height: "30px",
width: "30px",
background: "black",
borderRadius: "50%"
}}
/>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The crux of the logic is e.movementX which returns the amount of distance moved along x axis by the mouse since the last occurrence of that event. Use that to set the left position of the dragHeader
tl;dr: I'm finding that consecutive elements on my page with position: relative are overlapping, and really don't understand why.
Details: I'm trying to write a re-usable React component SlidePair that allows you to toggle between two other components with a slide animation, passing data between them. I'm using react-reveal for the animation, and positioning the elements at the same place in the page, so the exit of one accompanies the entry of the other. To make the divs overlap I followed the approach of this SO question.
My code, in the context of an App.js file from create-react-app, is below. You see that in the main App component I want to stack two SlidePair elements, which turn out to overlap instead. Everything else are just some example components for me to plug in; I've only included them for the sake of anyone who just wants to copy and paste the whole thing to get it running.
import React, {Component} from 'react';
import Slide from 'react-reveal/Slide';
class RedBox extends Component {
constructor(props){
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
text: props.text
}
}
handleChange(event) {
this.setState({text: event.target.value});
}
render(){
const { toggleState, text, style} = this.props;
return(
<div style={style}
onClick={()=>{console.log('red clicked'); toggleState({text: this.state.text})}}>
<input onChange={this.handleChange}
type="text" value={this.state.text}
onClick={(event)=>{event.stopPropagation()}}
style={{zIndex: '999'}}
/>
{ text }
</div>
);
}
}
const BlueBox = ({toggleState, passedProps, style })=> {
return (
<div onClick={toggleState} style={style}>
{ passedProps.text }
</div>
);
};
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
const coords = {
x: event.clientX,
y: event.clientY
};
this.props.toggleState(coords);
}
render() {
return (
<div style={{ height: '100px' }} onClick={this.handleClick}>
<h1>Click me!</h1>
</div>
);
}
}
const MouseInformer = ({toggleState, passedProps}) => (
<div>
You clicked {passedProps.x}, {passedProps.y}!
<button onClick={toggleState}>Go Back</button>
</div>
);
class SlidePair extends Component {
constructor(props){
super(props);
this.state = { left: true, passedProps: {}};
this.toggleState = this.toggleState.bind(this);
}
toggleState(passedProps){
const left = !this.state.left;
console.log(`Toggling left to ${left}`);
this.setState({ left, passedProps });
}
render(){
const {left, passedProps } = this.state;
return(
<div style={{position: 'relative'}}>
<Slide left when={left} >
<div style={ {position: 'absolute', top: '0px', right: '0px', width: '100%', zIndex: left ? '998' : -1 }}>
{this.props.renderLeft(this.toggleState, passedProps)}
</div>
</Slide>
<Slide right when={!left}>
<div style={{position: 'absolute', top: '0px', right: '0px', width: '100%', zIndex: left ? -1 : 1}}>
{ this.props.renderRight(this.toggleState, passedProps) }
</div>
</Slide>
</div>
)
}
}
class App extends Component {
render(){
const redBox = (toggleState, passedProps)=>(
<RedBox toggleState={toggleState}
style={{width: '100%', border: '5px solid red', height: '100px'}}/>
);
const blueBox = (toggleState, passedProps) => (
<BlueBox
toggleState={toggleState}
passedProps={passedProps}
style={{width: '100%', border: '5px solid blue', height: '100px'}}
/>
);
const mouseTracker = (toggleState, passedProps) => (
<MouseTracker toggleState={toggleState} passedProps={passedProps} style={{top: '300px'}}/>
);
const mouseInformer = (toggleState, passedProps) => (
<MouseInformer toggleState={toggleState} passedProps={passedProps} style={{top: '300px'}}/>
);
return (
<div className="App">
<SlidePair renderLeft={redBox} renderRight={blueBox}/>
<br/>
<SlidePair renderLeft={mouseTracker} renderRight={mouseInformer} />
</div>
);
}
}
export default App;
Thanks in advance for any help.