React Beautiful DnD delay when dropping - css

I am using react-beautiful-dnd. I have it working, except when I drag and drag an item into one of my lists, the item is positioned incorrrectly, has a short delay, then jumps to the correct position.
Here is what it looks like: Link to issue
As you can see, after the item is dropped in a list, the item readjusts itself to fit within the div.
Here is the code for the item:
import React, { useState } from "react";
import styled from "styled-components";
import { Draggable } from "react-beautiful-dnd";
const Container = styled.div`
margin: 0 0 8px 0;
background-color: rgba(140, 240, 255);
`;
const Title = styled.div`
font-size: 1.5rem;
`;
const Gradient = styled.div`
background: black;
height: 2px;
margin: 0.5rem;
`;
const Description = styled.div`
font-size: 1rem;
`;
const Ticket = ({ ticket, setCategories, id, index }) => {
const [isDeleted, setIsDeleted] = useState(false);
const handleDelete = (e) => {
e.preventDefault();
fetch(`/tickets/${ticket.id}`, {
method: "DELETE",
}).then(
fetch("/categories")
.then((r) => r.json())
.then(setCategories)
);
setIsDeleted(true);
};
return (
<Draggable draggableId={id.toString()} index={index}>
{(provided, snapshot) =>
isDeleted ? null : (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Container
style={{
backgroundColor: snapshot.isDragging
? "aquamarine"
: "rgba(140, 240, 255)",
}}
>
<Title>{ticket.title}</Title>
<Gradient></Gradient>
<Description>{ticket.description}</Description>
<button onClick={handleDelete}>Delete</button>
</Container>
</div>
)
}
</Draggable>
);
};
export default Ticket;
And here is for the list:
import React, { useState } from "react";
import styled from "styled-components";
import Ticket from "./Ticket";
import { Droppable } from "react-beautiful-dnd";
import { transformData } from "./Categories";
const Container = styled.div`
background-color: rgba(255, 255, 255, 0.8);
border-radius: 0.25em;
box-shadow: 0 0 0.25em rgba(0, 0, 0, 0.25);
text-align: center;
width: 20rem;
font-size: 1.5rem;
padding: 4px;
margin: 1rem;
`;
const Gradient = styled.div`
background: black;
height: 2px;
margin: 1rem;
`;
const FormContainer = styled.div`
margin: 1rem;
border: 1px solid black;
backgroundColor: rgba(140, 240, 255);
`;
const Button = styled.button`
margin-left: 1rem;
`;
const DropDiv = styled.div`
min-height: 50vh;
padding: 4px;
`;
const Category = ({ category, user, setCategories, id }) => {
const [isClicked, setIsClicked] = useState(false);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTicket = {
title: title,
description: description,
user_id: user.id,
category_id: id,
};
fetch("/tickets", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newTicket),
}).then(
fetch("/categories")
.then((r) => r.json())
.then(transformData)
.then(setCategories)
);
setIsClicked(false);
};
return (
<Container>
{category.title}
<Button onClick={() => setIsClicked(!isClicked)}>Add</Button>
<Gradient></Gradient>
{isClicked ? (
<FormContainer>
<form onSubmit={handleSubmit}>
<label>Title</label>
<input onChange={(e) => setTitle(e.target.value)}></input>
<label>Description</label>
<input onChange={(e) => setDescription(e.target.value)}></input>
<button type="submit">Submit</button>
</form>
</FormContainer>
) : null}
<Droppable droppableId={id.toString()}>
{(provided, snapshot) => (
<DropDiv
{...provided.droppableProps}
ref={provided.innerRef}
style={{
background: snapshot.isDraggingOver ? "lightblue" : "",
}}
>
{category.tickets.map((ticket, index) => {
return (
<Ticket
ticket={ticket}
key={ticket.id}
setCategories={setCategories}
id={ticket.id}
index={index}
/>
);
})}
{provided.placeholder}
</DropDiv>
)}
</Droppable>
</Container>
);
};
export default Category;
I have tried flexbox styling and messed with margin and padding. If i remove the margin and padding it seems to go away, but in beautiful-dnd examples they all have space between items and theres no delay like this. Does anyone have any ideas?

It looks like the placeholder might not have the 8px bottom margin that the rest of the Draggables have.
You'll notice that when you pick up something (without changing it's position) from somewhere other than the end of the list, the list will shift up a bit right away, and when you drop something at the end of a list, you don't see the issue.
The placeholder gets its margins from the item being dragged. You can see this happening by looking at the inline styles on the placeholder element that appears at the end of the droppable while you are dragging.
So, you might want to try putting the provided innerRef, draggableProps, and dragHandleProps on the Ticket Container itself instead of a parent div, as it's possible that because they are on a different element, react-beautiful-dnd isn't taking the margins in to account.

The delay could be caused because your list is changing size while dragging an element, and this provokes the library to recalculate and animate slower.
The solution is to avoid this size change while dragging.
The causes of the issue could be:
---- Cause A ----
The {provided.placeholder} auto inserted in the DOM while dragging doesn't have the same margins/padding that the other Draggable elements
Solution
Be sure to add the styles that separate the elements (margin/padding) to the element that you are applying the provided innerRef, draggableProps, and dragHandleProps because in this way the {provided.placeholder} will inherit those styles.
---- Cause B ----
You are using flexbox or css-grid with a gap property to separate the elements.
Solution
Stop using gap and just add margin to the element with provided innerRef, draggableProps, and dragHandleProps (do not use inline styles but css classes)
Extra: To confirm that size change is the cause
Drag some elements from somewhere other than the end of the list and notice how another element will move up a bit.
Also, when you drop something at the end of a list, you don't see the problem.

I was having this same issue- but as seen in the docs, inline styling with provided props did the trick for me:
const ListItem = ({ item, index }) => {
return (
<Draggable draggableId={item.id} className="draggableItem" index={index}>
{(provided, snapshot) => {
return (
<div
ref={provided.innerRef}
snapshot={snapshot}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
userSelect: "none",
padding: 12,
margin: "0 0 8px 0",
minHeight: "50px",
borderRadius: "4px",
backgroundColor: snapshot.isDragging
? "rgb(247, 247, 247)"
: "#fff",
...provided.draggableProps.style,
}}
>
<div className="cardHeader">Header</div>
<span>Content</span>
<div className="cardFooter">
<span>{item.content}</span>
<div className="author">
{item.id}
<img className="avatar" />
</div>
</div>
</div>
);
}}
</Draggable>
);
};

Related

How to dynamically vertically align content in a div using transform?

I have a simple login screen with a title, input field and button. Once a user clicks the button, the assumption is that the they become authenticated and should move onto the next screen. There is an intermediate transition that I'm trying to implement, which clears the input field and button and translates the title into the center of the div.
Currently, I calculate the container's height and the inner content's height with useRef and then conditionally change the height of Container if the user is authenticated.
Right now, for Title I'm doing,
transform: translateY(${authenticated ? 42 : 0}px);
But 42 could change in the future depending on stuff I might add or different screens that the user is viewing the page on.
Is there a better, more assuring way of doing this?
import * as React from "react";
import styled from "#emotion/styled";
const Container = styled.div`
height: 100vh;
display: grid;
place-items: center;
`;
const Box = styled.div<{
authenticated: boolean;
containerHeight: number;
innerContentHeight: number;
}>`
${({ authenticated, containerHeight, innerContentHeight }) => `
border: 1px solid red;
transition: 0.3s ease-in-out;
width: 15rem;
height: ${
containerHeight
? `${
authenticated
? containerHeight - innerContentHeight
: containerHeight
}px`
: "auto"
}
`}
`;
const Title = styled.h2<{ authenticated: boolean }>`
${({ authenticated }) => `
transform: translateY(${authenticated ? 42 : 0}px);
margin-bottom: 6rem;
`}
`;
const Input = styled.input``;
const Button = styled.button`
width: 100%;
margin-top: 1rem;
`;
export default function App() {
const [containerHeight, setContainerHeight] = React.useState(0);
const [innerContentHeight, setInnerContentHeight] = React.useState(0);
const [authenticated, setAuthenticated] = React.useState(false);
const handleRef = React.useCallback(
(isContainer: boolean) => (ref: HTMLDivElement) => {
if (!ref || (containerHeight && innerContentHeight)) return;
const height = Math.ceil(ref.getBoundingClientRect().height);
console.log("ref", ref);
console.log("height", height);
if (isContainer) {
setContainerHeight(height);
} else {
setInnerContentHeight(height);
}
},
[containerHeight, innerContentHeight]
);
const handleLogin = React.useCallback(() => {
setAuthenticated(true);
}, []);
return (
<Container>
<Box
ref={handleRef(true)}
authenticated={authenticated}
containerHeight={containerHeight}
innerContentHeight={innerContentHeight}
>
<Title authenticated={authenticated}>Welcome</Title>
{!authenticated && (
<div ref={handleRef(false)}>
<Input value={"test#test.com"} />
<Button onClick={handleLogin}>Login</Button>
</div>
)}
</Box>
</Container>
);
}

react styled-component nested item's props

My problem is about props. I want to use nested components with props in styled-components. For example:
const MenuItem = ({ item }) => {
const router = useRouter();
const [isOpen, setIsOpen] = useState(false);
const isActive = router?.asPath === item?.path;
return (
<MenuItemWrapper key={item?.slug} onClick={() => setIsOpen(!isOpen)}>
<Link href={item?.path}>
<InnerMenuItem isActive={isActive}>
{item?.prefix && <Prefix>{item?.prefix}</Prefix>}
{item?.label}
{item?.children && <RightArrow isOpen={isOpen} />}
</InnerMenuItem>
</Link>
<Children>
{
item?.children?.map((child) => <MenuItem item={child} />)
}
</Children>
</MenuItemWrapper>
);
};
export default MenuItem;
this is MenuItem component. I use MenuItem component as a recursion component.
in styled-component i tried this but it doesnt work. I want to apply different style in Children > InnerMenuItem but it not working
export const Children = styled.div`
display: flex;
flex-direction: column;
margin-left: 65px;
${MenuItemWrapper} {
font-size: 16px;
padding: 9px 0;
&:not(:first-child) {
border-top:none;
}
}
${InnerMenuItem} {
${({ isActive }) => // HOW CAN I USE THIS PROPS HERE
isActive &&
css`
color: orange
`};
}
`;
from styled components official documentations:
"If the styled target is a simple element (e.g. styled.div), styled-components passes through any known HTML attribute to the DOM. If it is a custom React component (e.g. styled(MyComponent)), styled-components passes through all props."
example :
const Input = styled.input`
color: ${props => props.inputColor || "palevioletred"};
`;
return(
<Input inputColor="rebeccapurple" />
)
another way is by Extending Styles, example :
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
return(
<div>
<Button>Normal Button</Button>
<TomatoButton>Tomato Button</TomatoButton>
</div>
);
more about styled-components read here
have you tried
${InnerMenuItem} {
color: ${({isActive})=> isActive ? 'orange' : undefined}
};

React.js & styled-components - Styled Input with (the illusion of ) Element inside it

so what i'm trying to do is to create an styled input that seem to have a fixed text (div / children / etc...) inside it.
one image can be self explenatory so this is how it looks like right now:
it seems simple, and the basic idea i quite is:
i got some Wrapper component that has 2 children - the input itself & some element.
like so:
const Input = (props: InputProps) => {
return (
<Wrapper>
<InputStyled {...props} />
<InnerElement>cm</InnerElement>
</Wrapper>
);
};
export default Input;
So where is the problem?
The problem is when i'm trying to set width to this component.
It destroys everything.
is looks like so:
so the wrapper should get a width prop and keep the input and the text element inside of it.
here is a codesandbox i created:
https://codesandbox.io/s/reactjs-input-with-element-inside-forked-2kr6s?file=/src/App.tsx:0-1795
it'll be nice if someone understand what i'm doing wrong.
here are some files:
Input.tsx
import React, { InputHTMLAttributes, CSSProperties } from "react";
import styled from "styled-components";
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
style?: CSSProperties;
label?: string;
value: string | number;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
text?: string;
isDisabled?: boolean;
hasError?: boolean;
errorLabel?: string | Function;
placeholder?: string;
width?: string;
}
const defaultProps: InputProps = {
text: "",
onChange: () => null,
value: ""
};
const Wrapper = styled.div<Partial<InputProps>>`
outline: 1px dashed red;
box-sizing: border-box;
justify-self: flex-start; // This is what prevent the child item strech inside grid column
border: 2px solid lightblue;
background: lightblue;
border-radius: 8px;
display: inline-flex;
align-items: center;
max-width: 100%;
width: ${({ width }) => width};
`;
const InputStyled = styled.input<Partial<InputProps>>`
box-sizing: border-box;
flex: 1;
outline: 1px dashed blue;
padding: 8px;
border: none;
border-radius: 8px;
max-width: 100%;
:focus {
outline: none;
}
`;
const InnerElement = styled.div`
outline: 1px dashed green;
box-sizing: border-box;
${({ children }) =>
children &&
`
padding: 0 8px;
font-size: 13px;
`};
`;
const Input = (props: InputProps) => {
return (
<Wrapper width={props.width}>
<InputStyled {...props} />
<InnerElement>{props.text}</InnerElement>
</Wrapper>
);
};
Input.defaultProps = defaultProps;
export default Input;
App.tsx
import * as React from "react";
import "./styles.css";
import Input from "./Input";
import styled from "styled-components";
const ContainerGrid = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 50px;
`;
export default function App() {
const [value, setValue] = React.useState("");
const handleChange = (e: any) => {
setValue(e.target.value);
};
const renderInputWithElement = () => {
return (
<ContainerGrid>
<Input
placeholder="input with element inside"
value={value}
onChange={handleChange}
width={"60px"} // Play with this
text="inch"
/>
<Input
placeholder="input with element inside"
value={value}
onChange={handleChange}
width={"110px"} // Play with this
text="cm"
/>
<Input
placeholder="input with element inside"
value={value}
onChange={handleChange}
width={"200px"} // Play with this
text="mm"
/>
<Input
placeholder="input with element inside"
value={value}
onChange={handleChange}
width={"100%"} // Play with this
text="El"
/>
<Input
placeholder="input with element inside"
value={value}
onChange={handleChange}
width={"100%"} // Play with this
/>
</ContainerGrid>
);
};
return (
<div className="App">
<h1>StyledCode</h1>
<h3>Input with an element inside</h3>
<p>
This is kind of an illusion since input cannot have child element inside
of it really.
</p>
<hr style={{ marginBottom: "32px" }} />
{renderInputWithElement()}
</div>
);
}
I Fixed it for you:
https://codesandbox.io/s/reactjs-input-with-element-inside-forked-h2het
First problem was that your were setting the same width to Wrapper and to InputStyled (by passing ...props) Then things get messy. Obviously, both parent and children which has some other element next to it can't have same width. InputStyled's won't be rendered with width you give it, but lesser, as it's being stretched to left by InnerElement.
If you need to pass it all the props, including the width, than you can just remove that width prop by setting width to unset
<InputStyled {...props} width="unset" />
However, there was another issue with padding interfering with width calculation. See answer to another question:
According to the CSS basic box model, an element's width and height
are applied to its content box. Padding falls outside of that content
box and increases the element's overall size.
As a result, if you set an element with padding to 100% width, its
padding will make it wider than 100% of its containing element. In
your context, inputs become wider than their parent.
You can go along with solution there. However, the one simple solution is to use width lesser than your padding, so width: calc(100% - 16px). This does the trick.
So i've found the answer, and its quite simple.
since the browser initializes an input with a width, it cause problems
with the flexbox behavor - the input element can't shrink below its default width and is > forced to overflow the flex container.
So i added min-width: 0 to InputStyled.
thats it.
#justdv thanx for your answer, you right about the width being on the wrong place, i fixed it but the sandbox wasnt updated. i missed that.
thanx anyway for your time :)
here is my fixed sandbox:
https://codesandbox.io/s/reactjs-input-with-element-inside-forked-44h8i?file=/src/Input.tsx
Thanx again for reading.

css not rendering in react component

When i apply the below CSS in App.css they render perfectly, however when I apply the same styles directly in my component (below) the CSS does not render.
const getStyles = () => {
const BLACK = "#000000";
const VW = "vw";
const VH = "vh";
const REM = "rem";
return {
editor: {
width: `70${VW}`,
height: `50${VH}`,
margin: `2${REM}`,
padding: `1${REM}`,
fontSize: `1.2${REM}`,
boxShadow: `0 .1${REM} .4rem ${BLACK}`,
border: `1px solid ${BLACK}`,
overflowY: `auto`
}
};
};
const styles = getStyles();
return (
<>
<div className="center">
<div className={styles.editor} contentEditable={true} suppressContentEditableWarning={true}>
<h1>{introText}</h1>
<p>{subText}</p>
</div>
</div>
</>
)
}
To get the effect you desire you should do something like this.
const getStyles = () => {
const BLACK = "#000000";
const VW = "vw";
const VH = "vh";
const REM = "rem";
return {
width: `70${VW}`,
height: `50${VH}`,
margin: `2${REM}`,
padding: `1${REM}`,
fontSize: `1.2${REM}`,
boxShadow: `0 .1${REM} .4rem ${BLACK}`,
border: `1px solid ${BLACK}`,
overflowY: `auto`
};
};
const styles = getStyles();
return (
<>
<div className="center">
<div style={styles.editor} contentEditable={true} suppressContentEditableWarning={true}>
<h1>{introText}</h1>
<p>{subText}</p>
</div>
</div>
</>
)
}
More info at this link:
https://reactjs.org/docs/dom-elements.html#style

Generating a grid-container style when mapping through an array

Summary
I have the following chart image (react-vis):
My React code (below) is creating this with a map through an array. How should I modify the code so the charts go across the page and are contained in some kind of fluid wrapper like this:
What have I looked at/tried?
I have basic understanding of HTML and CSS but would not know how to approach this kind of task and modify the code.
Would I need to use something like this and integrate with the code above?
<div class="grid-container">
<div className="grid-item">1</div>
<div className="grid-item">2</div>
<div className="grid-item">3</div>
<div className="grid-item">4</div>
</div>
I would like to understand an effective way to do this please using CSS, bootstrap or whatever would be considered best practice.
Code:
MyComp.js
import React, { useState, useEffect } from "react"
import Example from "./plotBar.js"
function getJson() {
return fetch("http://secstat.info/testthechartdata3.json")
.then(response => response.json())
.catch(error => {
console.error(error)
})
}
const MyComp = () => {
const [list, setList] = useState([])
useEffect(() => {
getJson().then(list => setList(list))
}, [])
return (
<div>
{list.map((data, index) => (
<Example
key={index}
data={data.map(({ id, count }) => ({
x: id,
y: count,
}))}
/>
))}
</div>
)
}
export default MyComp
plotBar.js
import React from "react"
import {
XYPlot,
XAxis,
YAxis,
VerticalGridLines,
HorizontalGridLines,
VerticalBarSeries,
} from "react-vis"
export default function Example({ data }) {
return (
<XYPlot margin={{ bottom: 70 }} xType="ordinal" width={300} height={300}>
<VerticalGridLines />
<HorizontalGridLines />
<XAxis tickLabelAngle={-45} />
<YAxis />
<VerticalBarSeries data={data} />
</XYPlot>
)
}
The data looks like this:
URL for JSON
http://secstat.info/testthechartdata3.json
You should read about flex and flex-flow, after that it just applying minor styling, this is CSS-in-JS example:
const Item = styled(Example)``;
const Container = styled.div`
display: flex;
flex-flow: row wrap;
background: lightgray;
padding: 0.5rem;
${Item} {
margin: 0.5rem;
padding: 1rem;
background: white;
}
`;
const Item = styled(Example)``;
const Container = styled.div`
display: flex;
flex-flow: row wrap;
background: lightgray;
padding: 0.5rem;
${Item} {
margin: 0.5rem;
padding: 1rem;
background: white;
}
`;
export default function Example({ data, className }) {
return (
<XYPlot className={className} xType="ordinal" width={200} height={200}>
<VerticalGridLines />
<HorizontalGridLines />
<XAxis tickLabelAngle={-45} />
<YAxis />
<VerticalBarSeries data={data} />
</XYPlot>
);
}
const list = // fetch on mount
const MyComp = () => {
return (
<Container>
{list.map((data, index) => (
<Item
key={index}
data={data.map(({ id, count }) => ({
x: id,
y: count,
}))}
/>
))}
</Container>
);
};

Resources