How to implement react-dnd useDragLayer? - reactjs

I have a component that currently uses the useDrag hook to connect to react-dnd. It works well, except for previews. I want to implement useDragLayer instead to see if it would help with my preview problems, as many online threads suggest.
This is my current (simplified) useDrag implementation:
const [{ isDragging }, connectDragSource, connectPreview] = useDrag({
item,
collect: monitor => ({
isDragging: monitor.getItem()?.index === item.index,
})
})
return (
<Wrapper ref={connectPreview} isDragging={isDragging}>
<DragHandle ref={connectDragSource} />
</Wrapper>
)
How do I use useDragLayer in this context, in a way that might help with my previews? The docs example makes little sense to me...
How do I connect my rendered components using useDragLayer api? useDragLayer doesn't return drag source and preview connector functions (like useDrag does on index 1 and 2 of the returned array), and its collect function doesn't provide a DragSourceConnector instance either. So what do I do with the hook/returned value after I call it?

I just resolved this and want to share it to help others :)
You will need to do couple of things for this to fully work.
Disable the default preview behavior by adding the following useEffect
import { getEmptyImage } from "react-dnd-html5-backend";
const [{ isDragging }, drag, dragPreview] = useDrag(() => ({
type: "BOX",
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
}));
useEffect(() => {
dragPreview(getEmptyImage(), { captureDraggingState: true });
}, []);
Create the custom default layer
export const CustomDragLayer = (props: {}) => {
const {
itemType,
isDragging,
initialCursorOffset,
initialFileOffset,
currentFileOffset,
} = useDragLayer((monitor) => ({
item: monitor.getItem(),
itemType: monitor.getItemType(),
initialCursorOffset: monitor.getInitialClientOffset(),
initialFileOffset: monitor.getInitialSourceClientOffset(),
currentFileOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging(),
}));
if (!isDragging) {
return null;
}
return (
<div style={layerStyles}>
<div
style={getItemStyles(
initialCursorOffset,
initialFileOffset,
currentFileOffset
)}
>
<div>Your custom drag preview component logic here</div>
</div>
</div>
);
};
const layerStyles: CSSProperties = {
position: "fixed",
pointerEvents: "none",
zIndex: 100,
left: 0,
top: 0,
width: "100%",
height: "100%",
border: "10px solid red",
};
function getItemStyles(
initialCursorOffset: XYCoord | null,
initialOffset: XYCoord | null,
currentOffset: XYCoord | null
) {
if (!initialOffset || !currentOffset || !initialCursorOffset) {
return {
display: "none",
};
}
const x = initialCursorOffset?.x + (currentOffset.x - initialOffset.x);
const y = initialCursorOffset?.y + (currentOffset.y - initialOffset.y);
const transform = `translate(${x}px, ${y}px)`;
return {
transform,
WebkitTransform: transform,
background: "red",
width: "200px",
};
}
Add the <CustomDragLayer /> to the top-level component
You will need to include the ref={drag} to the component you want to drag and remove the connectPreview ref completely.
Hopefully, this helps you.

Related

How do i implement react-dnd-text-dragpreview in a Functional Component?

i'm using react-dnd and i'm able to put an image when draging, but now i would like to instead of a image i want a custom text.
i found this component react-dnd-text-dragpreview, but the example is for react class component.
i've tried to put "dragPreviewImage" in the src of "DragPreviewImage" , but doesn't work.
can someone help me on this ?
thanks in advance !
https://www.npmjs.com/package/react-dnd-text-dragpreview
sample code
...
import { DragPreviewImage, useDrag } from 'react-dnd';
import { boxImage } from '../components/boxImage';
import { createDragPreview } from 'react-dnd-text-dragpreview'
function FieldDrag({ field, dropboxField, onDragEnd = () => null, setFieldValue = () => null, cargoCategories }) {
const [{ isDragging }, drag, preview] = useDrag(() => ({
type: 'field',
item: { id: field.id, dragCargoInfoId: field.dragCargoInfoId, dragCargoInfo: field.childDragCargoInfo },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
end: (item) => endDrag(item),
}));
const endDrag = (item) => {
onDragEnd(item);
};
const styles = {
fontSize: '12px'
}
const dragPreviewImage = createDragPreview('Custom Drag Text', styles);
.....
return (
<>
<DragPreviewImage connect={preview} src={boxImage} />
<span ref={drag} className="flex-item" style={{ ...computedStyle, ...styleDropboxField }}>
{getField(field, extraStyle, isboxed, cargoCategories)}
</span>
</>
);
drag with image
I found the solution in the codesandbox --> https://codesandbox.io/s/uoplz?file=/src/module-list.jsx:2522-2619
just put src image "dragPreviewImage.src".
<DragPreviewImage connect={preview} src={dragPreviewImage &&
dragPreviewImage.src} />

React material-ui (MUI) 5 conditional CSS classes

I'm moving from React material-ui 4 to MUI 5.
How do I achieve this type of pattern using the new styled API (or whatever makes sense)?
I'm using Typescript.
const useStyles = makeStyles(theme => ({
topBar: {
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.sharp,
duration: TRANSITION_DURATION,
}),
marginLeft: DRAWER_WIDTH,
},
topBarShift: {
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.easeOut,
duration: TRANSITION_DURATION,
}),
marginLeft: 0,
},
}));
function Header({ drawer }: IHeader) {
const classes = useStyles();
...
return (
<div className={`${classes.topBar} ${!drawer && classes.topBarShift}`}>
...
</div>
);
}
If I understand your question clearly, you just want a conditional string.
This can be done by creating a util function for reusability:
type ConditionalClasses = {
[className: string]: bool
}
const conditionalClass = (classes: ConditionalClass) =>
Object.keys(classes).reduce((combinedClassName, className) => classes[className] ? `${combinedClassName} ${className}` : combinedClassName, "")
Usage goes as follows
// Output: "a"
const newClassName = conditionalClasses({a: true, b: false})
Alternatively, you could use clsx
-- Edit
Looks like I misread and hence misunderstood the question.
If you want to use the styled API styles conditionally, you can use the overridesResolver option provided by the styled API.
const CustomDivComponent = styled("div", {
overridesResolver: (props, styles) => {
// Do your conditional logic here.
// Return the new style.
return {};
}
})(({ theme }) => ({
// Your original styles here...
}));
More documentation can be found here

Why is React component only working after I refresh page?

I have a component called Tools which is accessed when I am on the route tools/:id. On this page, I have buttons with Links to this page, but it passes in a different id to the URL when clicked so that a different tool will be shown.
The getTool method just returns the correct component, and the component will only have one thing, an iFrame to show a calculator from another website.
So when I go back and forth between tools, the tools aren't loading until I click refresh. Otherwise, I get an error that says TypeError: Cannot read property 'style' of null. This is because I have document.getElementById('calc-SOME-NUMBER') but SOME_NUMBER is still referring to the last tool I was on. And this statement is within each tool and you can this below in BCalc.
I've checked the state, and when I go back and forth between tools, everything is correct; the correct tool is placed in the reducer. Any idea why this is happening? I was using history.go() as a workaround because I can't see any reason it still is lingering on to the old tools id.
const Tool = ({
getContent,
closeContent,
content: { content, loading, contents },
user,
match,
}) => {
const history = useHistory();
useEffect(() => {
scrollToTop();
getContent(match.params.id);
return () => {
logView(user, match.params.id, "tool");
closeContent();
history.go(); // doesn't work without this line
};
}, [getContent, closeContent, match.params.id, user]);
let ToolComponent;
const listItemStyle = { paddingTop: "40px", paddingBottom: "40px" };
return loading || content === null ? (
<Spinner />
) : (
<Container
style={{ marginTop: "3%" }}
fluid="true"
className="contentCardBody"
>
<Card>
<Card.Body>
{(ToolComponent = getTool(content.content_id.path))}
<ToolComponent />
</Card.Body>
</Card>
</Container>
);
};
Tool.propTypes = {
content: PropTypes.object.isRequired,
user: PropTypes.object.isRequired,
getContent: PropTypes.func.isRequired,
closeContent: PropTypes.func.isRequired,
};
const mapStateToProps = (state) => ({
content: state.content,
user: state.auth.user,
});
export default connect(mapStateToProps, {
getContent,
closeContent,
})(Tool);
Also, here is an example of what is returned from getTool():
const BCalc = () => {
const eventMethod = window.addEventListener ? 'addEventListener' : 'attachEvent';
const eventer = window[eventMethod];
const messageEvent = eventMethod === 'attachEvent' ? 'onmessage' : 'message';
eventer(
messageEvent,
(e) => {
if (e.origin === 'https://EXAMPLE.com') {
if (e.data.startsWith('height;')) {
document.getElementById('calc-SOME_NUMBER').style.height = `${e.data.split('height;')[1]}px`;
} else if (e.data.startsWith('url;')) {
window.location.href = e.data.split('url;')[1];
}
}
},
!1,
);
return (
<div>
<iframe
id="calc-SOME_NUMBER"
src="https://EXAMPLE_CALC"
title="tool"
scrolling="no"
style={{
width: '100%',
border: 'none',
marginTop: '-2%',
zIndex: '-1',
}}
/>
</div>
);
};
export default BCalc;

How to do a simple fade in transition on React Motion?

I have this basically:
<Motion defaultStyle={{opacity: 0}} style={{ opacity: spring(1, { stiffness: 210, damping: 20 }) }}>{style => {
return <div style={style}>
</Motion>
But the opacity chops and takes a while.
I just want to say "fade in ease-in-out 300ms". Can anything like this be done with react-motion? Or must you use react-transition-group?
I don't think that can be changed, but the velocity seems can be adjusted from stiffness and damping, https://github.com/chenglou/react-motion/issues/265
You can try a helper to figure out those values, http://chenglou.github.io/react-motion/demos/demo5-spring-parameters-chooser/
The trouble seems to me is the mount /dismount issue, but if you don't care, you could just setmount to be false.
const Fade = ({
Style, on, mount, children
}) => {
const [animating, setAnimating] = useState(true)
const onRest = () => { setAnimating(false) }
useEffect(() => { setAnimating(true) }, [on])
if (mount) {
if (!on && !animating) {
return null
}
}
return (
<Style
on={on}
onRest={onRest}
>
{children}
</Style>
)
}
Fade.propTypes = {
Style: elementType,
on: bool,
mount: bool,
}
You should not use the given "style" as the style prop
You should use it as such:
<Motion defaultStyle={{opacity: 0}} style={{ opacity: spring(1, { stiffness: 210, damping: 20 }) }}>{style => {
return <div style={{opacity: style.opacity}>
</Motion>
see my example here: fade example with delay using hooks

React - prevstate issues, getting back old value

I have an SVG map with paths, and those paths change colors when I hover over them.
It changes state of specific section, for example my state looks like that:
POL3139: {
color: '#fbb9c5'
},
I am trying to switch back to the base color after I leave the path.
Here I am changing
onHover = (event) => {
event.stopPropagation();
const e = event.target.id
this.setState(prevState => ({
[e]: {
...prevState,
color: '#650df9'
},
}));
}
It totally works and changes my color to the picked one.
But then I am trying to revert back to the original one.
I tried that by making a base color in the state:
POL3139: {
color: '#fbb9c5',
base: '#fbb9c5'
},
and then onMouseLeave:
onLeave = (event) => {
event.stopPropagation();
const e = event.target.id;
this.setState(prevState => ({
[e]: {
...prevState,
// color: prevState.base - doesn't work
// color: prevState.[e].base - doesn't work
// color: [prevState.e.base] - doesn't work
color: 'pink'
}
}));
}
I was trying many possible solutions but I can't get it to work.
I am still learning react and it might be an easy one but I can't figure it out.
I don't think you're deconstructing your prevState properly.
Here's an example to illustrate how to deconstruct:
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
state = {
style: {
color: "black",
base: "black",
cursor: "pointer"
}
};
handleMouseEnter = e => {
const { style } = this.state;
this.setState({ style: { ...style, color: "red" } });
};
handleMouseLeave = e => {
const { style } = this.state;
this.setState({ style: { ...style, color: style.base } });
};
render() {
return (
<div
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
style={this.state.style}
>
<h1>hello</h1>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
In this case style is equivalent to your [e].
In particular, look at the deconstructing here:
this.setState({ style: { ...style, color: style.base } });
There's a working example here.

Resources