react The swipe animation will be performed twice - reactjs

When you press the Circle button, the Box moves to the right and disappears from the screen.
Additional information (FW/tool version, etc.)
react
scss
Typescript
framer-motion
import "./style.scss";
import React, { FunctionComponent, useState } from "react";
import { useMotionValue, useTransform } from "framer-motion";
import { SwipeBox } from "./SwipeBox";
const App: FunctionComponent = () => {
const [cards, setCards] = useState([
const onClic = () => {
animateCardSwipe({ x: -1400, y: 0 });
}; <div
style={{
width: "400px",
height: "300px",
background: `${card.background}`
}}
>
) : (
</div>
);
};
export default App;

The problem is that the animation is happening on mount and you're updating state twice inside the animateCardSwipe function:
const animateCardSwipe = (animation: { x: number, y: number }) => {
setAnime({ ...anime, animation });
setTimeout(() => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
}, 200);
};
I personally like to use a more imperative approach here for starting the animation using animate:
const animateCardSwipe = (animation: { x: number, y: number }) => {
animate(x, animation.x, {
duration: 1,
onComplete: () => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
},
});
};
This implementation also waits for the animation to complete before rearranging the cards.
Full example:
import React, { FunctionComponent, useState } from "react";
import {
animate,
useAnimation,
useMotionValue,
useTransform,
MotionValue,
motion,
} from "framer-motion";
interface Props {
animate?: { x: number; y: number };
style: {
x?: MotionValue;
y?: MotionValue;
zIndex: number;
rotate?: MotionValue;
};
card: { text: string; background: string };
}
const SwipeBox: React.FunctionComponent<Props> = (props) => {
return (
<motion.div
animate={props.animate}
className="card"
style={{
...props.style,
background: "white",
borderRadius: "8px",
display: "grid",
top: 0,
left: 0,
placeItems: "center center",
position: "absolute",
width: "100%",
}}
transition={{ duration: 1 }}
>
{props.children}
</motion.div>
);
};
const App: FunctionComponent = () => {
const controls = useAnimation();
console.log(controls);
const [cards, setCards] = useState([
{ text: "Up or down", background: "red" },
{ text: "Left or right", background: "green" },
{ text: "Swipe me!", background: "gray" },
{ text: "Swipe me!", background: "purple" },
{ text: "Swipe me!", background: "yellow" },
]);
const x = useMotionValue(0);
const y = useMotionValue(0);
const animateCardSwipe = (animation: { x: number; y: number }) => {
animate(x, animation.x, {
duration: 1,
onComplete: () => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
},
});
};
const [anime, setAnime] = useState<{ animation: { x: number; y: number } }>({
animation: { x: 0, y: 0 },
});
const rotate = useTransform(x, [-950, 950], [-30, 30]);
const onClickLeft = () => {
animateCardSwipe({ x: 1400, y: 0 });
};
const onClickRight = () => {
animateCardSwipe({ x: -1400, y: 0 });
};
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<div>
{cards.map((card, index) =>
index === cards.length - 1 ? (
<SwipeBox
animate={anime.animation}
card={card}
key={index}
style={{ x, y, zIndex: index, rotate: rotate }}
>
<div
style={{
width: "400px",
height: "300px",
background: `${card.background}`,
}}
>
{card.text}
</div>
</SwipeBox>
) : (
<SwipeBox
card={card}
key={index}
style={{
zIndex: index,
}}
>
<div
style={{
width: "400px",
height: "300px",
background: `${card.background}`,
}}
>
{card.text}
</div>
</SwipeBox>
)
)}
</div>
<div style={{ zIndex: 999 }}>
<button onClick={onClickRight}>✖️</button>
<button onClick={onClickLeft}>○</button>
</div>
</div>
);
};
export default App;
Codesandbox Demo

Related

React component not passing value to correct component

I have a simple React app that uses react-dnd to build a grid of draggable squares that have an initial color and text value and two components to change the color and change the text.
The color change (via ColorPicker2, using react-color library) works okay. The text change (using TextInput from #carbon/react) doesn't work as desired.
I thought I was applying the same logic with both components, but whilst the color-picker updates the color and retains that color when the square is moved, the text seems to render inside the TextInput and not the square itself and I can't figure out the logical difference.
The Code Sandbox is here: https://codesandbox.io/s/wild-waterfall-z8s1de?file=/src/App.js
This is the current code:
ColorPicker2.js
import "./styles.css";
import React from "react";
import { BlockPicker } from "react-color";
const presetColors = ["#9E9E9E", "#4CAF50", "#FFEB3B", "#F44336", "#2196F3"];
const ColorPicker2 = (props) => {
const handleChangeComplete = (color) => {
if (props.updateColor) {
props.updateColor(color.hex);
}
};
return (
<div className="palette">
<BlockPicker
className="palette"
colors={presetColors}
onChangeComplete={handleChangeComplete}
presetColors={Object.values(presetColors)}
color={props.currentColor}
/>
</div>
);
};
export default ColorPicker2;
App.js
import "./styles.css";
import React, { useState } from "react";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import edit from "./edit.svg";
import palette from "./palette.svg";
import ColorPicker2 from "./ColorPicker2";
import { TextInput } from "#carbon/react";
const DndWrapper = (props) => {
return <DndProvider backend={HTML5Backend}>{props.children}</DndProvider>;
};
const DraggableSquare = ({ index, text, color, moveSquare }) => {
const [{ isDragging }, drag] = useDrag({
type: "square",
item: { index },
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
});
const [isHovered, setIsHovered] = useState(false);
const [, drop2] = useDrop({
accept: "square",
drop: (item, monitor) => {
const didDrop = monitor.didDrop();
if (!didDrop) {
moveSquare(item.index, index);
}
},
hover: (item, monitor) => {
setIsHovered(monitor.isOver());
},
collect: (monitor) => {
setIsHovered(monitor.isOver());
}
});
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
const [isTextInputOpen, setIsTextInputOpen] = useState(false);
const [newText, setNewText] = useState(text);
const opacity = isDragging ? 0.5 : 1;
return (
<div className="square-div" ref={drop2}>
<div
className="grey-square"
ref={drag}
style={{
opacity,
backgroundColor: color,
width: "200px",
height: "200px",
textAlign: "center",
paddingTop: "30px",
position: "relative",
border: isHovered ? "3px solid blue" : "none",
borderRadius: "5px",
}}
onMouseOver={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<img
src={edit}
onClick={() => {
setIsTextInputOpen(!isTextInputOpen);
if (!isTextInputOpen) {
moveSquare(index, newText, color, undefined);
}
}}
style={{
width: "15px",
height: "15px",
position: "absolute",
right: "5px",
top: "5px"
}}
alt="edit icon"
/>
{isTextInputOpen && (
<TextInput
id="newtext"
labelText=""
value={newText}
onChange={(e) => setNewText(e.target.value)}
/>
)}
<img
src={palette}
onClick={() => setIsPaletteOpen(!isPaletteOpen)}
style={{
width: "15px",
height: "15px",
position: "absolute",
right: "25px",
top: "5px"
}}
alt="palette icon"
/>
{isPaletteOpen && (
<ColorPicker2
className="palette"
currentColor={color}
updateColor={(newColor) =>
moveSquare(index, index, newText, newColor)
}
/>
)}
</div>
</div>
);
};
const Grid = () => {
const [grid, setGrid] = useState([
{ text: "1", color: "grey" },
{ text: "2", color: "grey" },
{ text: "3", color: "grey" },
{ text: "4", color: "grey" },
{ text: "5", color: "grey" },
{ text: "6", color: "grey" },
{ text: "7", color: "grey" },
{ text: "8", color: "grey" },
{ text: "9", color: "grey" },
{ text: "10", color: "grey" },
{ text: "11", color: "grey" },
{ text: "12", color: "grey" },
{ text: "13", color: "grey" },
{ text: "14", color: "grey" },
{ text: "15", color: "grey" }
]);
const moveSquare = (fromIndex, toIndex, newText, newColor) => {
setGrid((grid) => {
const newGrid = [...grid];
const item = newGrid[fromIndex];
newGrid.splice(fromIndex, 1);
newGrid.splice(toIndex, 0, {
text: newText || item.text,
color: newColor || item.color
});
return newGrid;
});
};
return (
<>
<DndWrapper>
<div
className="grid"
style={{
display: "grid",
gridTemplateColumns: "repeat(5, 190px)",
gridGap: "15px",
gridColumnGap: "20px",
gridRowGap: "10px",
position: "absolute"
}}
>
{grid.map((square, index) => (
<DraggableSquare
key={index}
index={index}
text={square.text}
color={square.color}
moveSquare={moveSquare}
//grid={grid}
//setGrid={setGrid}
/>
))}
</div>
</DndWrapper>
</>
);
};
export default Grid;
Any thoughts from fresh eyes would be helpful.
I think this is just a simple issue with using index as the key while mapping. Adjusting your code pen to have a unique key fixed it for me but the input text is not being saved anywhere so returned to the default text={square.text} when moved as expected.
Unique Id in objects:
const [grid, setGrid] = useState([
{ text: "1", color: "grey", id: crypto.randomUUID() },
{ text: "2", color: "grey", id: crypto.randomUUID() },
{ text: "3", color: "grey", id: crypto.randomUUID() },...])
Adding key to mapped object:
{grid.map((square, index) => (
<DraggableSquare
key={square.id}
index={index}
text={square.text}
color={square.color}
moveSquare={moveSquare}
//grid={grid}
//setGrid={setGrid}
/>}

How to make an onClick event on interactive map Leaflet?

I created interactive map. It work quite good but I cannot send currentId or Name of volveship I click. I get error that this property is undefined and I do not know how to fix this. I use this date https://github.com/ppatrzyk/polska-geojson/blob/master/wojewodztwa/wojewodztwa-min.geojson?short_path=898b1e2
import React, { useState } from 'react';
import {
MapContainer,
TileLayer,
Polygon
} from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import { statesData } from './data';
import './App.css';
const center = [52.237049, 21.017532];
export default function App() {
const [currentID, setCurrentID] = useState();
return (
<>
<MapContainer
center={center}
zoom={6}
style={{ width: '100vw', height: '100vh' }}
>
<TileLayer
url="https://api.maptiler.com/maps/basic/tiles.json?key=MyKey"
attribution='© MapTiler © OpenStreetMap contributors'
/>
{
statesData.features.map((state) => {
const coordinates = state.geometry.coordinates[0].map((item) => [item[1], item[0]]);
return (<Polygon
pathOptions={{
fillColor: '#ae4a84',
fillOpacity: 0.7,
weight: 2,
opacity: 1,
dashArray: 3,
color: 'white'
}}
positions={coordinates}
eventHandlers={{
mouseover: (e) => {
const layer = e.target;
layer.setStyle({
dashArray: "",
fillColor: "#003263",
fillOpacity: 0.7,
weight: 2,
opacity: 1,
color: "white",
})
},
mouseout: (e) => {
const layer = e.target;
layer.setStyle({
fillOpacity: 0.7,
weight: 2,
dashArray: "3",
color: 'white',
fillColor: '#ae4a84'
});
},
click: (e) => {
setCurrentID(e.target.id) }
}}
/>)
})
}
</MapContainer>
</>
);
}

react-spring and react-intersection-observer - tons of rerenders

JSFiddle
Code:
export default function App() {
const spring = useSpring({ from: { opacity: 0 }, to: { opacity: 1 } });
const [ref] = useInView();
rerenders++;
return (
<div style={{ height: "200vh" }}>
<div style={{ height: "150vh" }}></div>
<animated.div
ref={ref}
style={{
height: "50px",
width: "50px",
backgroundColor: "red",
opacity: spring.opacity
}}
>
Hello!
</animated.div>
</div>
);
}
Attaching useInView's ref (a hook from react-intersection-observer) causes constant rerendering of the component. Why is that so?
Using an IntersectionObserver yourself does not do such a thing:
const ref = useRef<any>();
useLayoutEffect(() => {
const obs = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
console.log(entry);
});
});
obs.observe(ref.current);
}, []);

Argument of type is not assignable to parameter of type BaseUI

I get this error when I run now from zeit now:
Argument of type '{ "#media only screen and (max-width: 767px)": { width: string; }; }' is not assignable to parameter of type '(props: object & { $theme: Theme; }) => any'.
Object literal may only specify known properties, and '"#media only screen and (max-width: 767px)"' does not exist in type '(props: object & { $theme: Theme; }) => any'. TS2345
const StyledHead = withStyle(BaseStyledHead, {
"#media only screen and (max-width: 767px)": {
width: "1000px"
}
});
I don't understand why I'm having this error, it's like it doesn't recognize width. I used yarn to install the packages. As you can see it's a typescript app, using BaseUI(Uber) UI framework.
Here are the component code:
import React, { useState } from "react";
import Moment from "react-moment";
import { styled, withStyle, createThemedUseStyletron } from "baseui";
import { Col as Column } from "../../../components/FlexBox/FlexBox";
import Input, { SIZE } from "../../Input/Input";
import gql from "graphql-tag";
import { useQuery } from "#apollo/react-hooks";
import { Wrapper, Header, Heading } from "../../WrapperStyle";
import {
StyledTable,
StyledHead as BaseStyledHead,
StyledHeadCell as BaseStyledHeadCell,
StyledBody as BaseStyledBody,
StyledRow,
StyledCell as BaseStyledCell
} from "baseui/table";
type CustomThemeT = { red400: string; textNormal: string; colors: any };
const themedUseStyletron = createThemedUseStyletron<CustomThemeT>();
const Status = styled("div", ({ $theme }) => ({
...$theme.typography.fontBold14,
color: $theme.colors.textDark,
display: "flex",
alignItems: "center",
lineHeight: "1",
textTransform: "capitalize",
":before": {
content: '""',
width: "10px",
height: "10px",
display: "inline-block",
borderRadius: "10px",
backgroundColor: $theme.borders.borderE6,
marginRight: "10px"
}
}));
const Col = styled(Column, () => ({
"#media only screen and (max-width: 767px)": {
marginBottom: "20px",
":last-child": {
marginBottom: 0
}
}
}));
const TableWrapper = styled("div", () => ({
width: "100%",
height: "400px",
padding: "0 25px 25px"
}));
const StyledHead = withStyle(BaseStyledHead, {
width: "100%",
"#media only screen and (max-width: 767px)": {
width: "1000px"
}
});
const StyledBody = withStyle(BaseStyledBody, {
width: "100%",
"#media only screen and (max-width: 767px)": {
width: "1000px"
}
});
const StyledHeadCell = withStyle(BaseStyledHeadCell, {
fontFamily: "'Lato', sans-serif",
fontWeight: 700,
color: "#161F6A !important"
});
const SmallHeadCell = withStyle(StyledHeadCell, {
maxWidth: "70px"
});
const StyledCell = withStyle(BaseStyledCell, {
fontFamily: "'Lato', sans-serif",
fontWeight: 400,
color: "#161F6A !important"
});
const SmallCell = withStyle(StyledCell, {
maxWidth: "70px"
});
const GET_ORDERS = gql`
query getOrders($status: String, $limit: Int, $searchText: String) {
orders(status: $status, limit: $limit, searchText: $searchText) {
id
creation_date
delivery_address
amount
payment_method
contact_number
status
customer_id
}
}
`;
export default function Orders() {
const [customer_id, setCustomerId] = useState("");
const [search, setSearch] = useState([]);
const [useCss, theme] = themedUseStyletron();
const sent = useCss({
":before": {
content: '""',
backgroundColor: theme.colors.primary
}
});
const failed = useCss({
":before": {
content: '""',
backgroundColor: theme.colors.red400
}
});
const packing = useCss({
":before": {
content: '""',
backgroundColor: theme.colors.textNormal
}
});
const paid = useCss({
":before": {
content: '""',
backgroundColor: theme.colors.blue400
}
});
const { data, error, refetch } = useQuery(GET_ORDERS);
if (error) {
return <div>Error! {error.message}</div>;
}
console.log(data && data.orders.map(item => Object.values(item)));
function handleSearch(event) {
const { value } = event.currentTarget;
setSearch(value);
refetch({ searchText: value });
}
return (
<Wrapper>
<Header style={{ padding: "25px 10px" }}>
<Col md={2}>
<Heading>Orders</Heading>
</Col>
<Col md={10}>
<Input
value={search}
size={SIZE.compact}
placeholder="Quick Search"
onChange={handleSearch}
clearable
/>
</Col>
</Header>
<TableWrapper>
<StyledTable>
<StyledHead $width="100%">
<SmallHeadCell>Id</SmallHeadCell>
<StyledHeadCell>Time</StyledHeadCell>
<StyledHeadCell>Delivery Address</StyledHeadCell>
<StyledHeadCell>Amount</StyledHeadCell>
<StyledHeadCell>Payment Method</StyledHeadCell>
<StyledHeadCell>Contact</StyledHeadCell>
<StyledHeadCell>Status</StyledHeadCell>
</StyledHead>
<StyledBody $width="100%">
{data &&
data.orders
.map(item => Object.values(item))
.map((row, index) => (
<StyledRow key={index}>
<SmallCell>{row[0]}</SmallCell>
<StyledCell>
<Moment format="Do MMM YYYY">{row[1]}</Moment>
</StyledCell>
<StyledCell>{row[2]}</StyledCell>
<StyledCell>{row[3]}</StyledCell>
<StyledCell>{row[4]}</StyledCell>
<StyledCell>{row[5]}</StyledCell>
<StyledCell>
<Status
className={
row[6] === 1
? sent
: row[6] === 2
? paid
: row[6] === 3
? packing
: row[6] === 4
? failed
: ""
}
>
{row[6] === 1
? "sent"
: row[6] === 2
? "paid"
: row[6] === 3
? "packing"
: row[6] === 4
? "failed"
: ""}
</Status>
</StyledCell>
</StyledRow>
))}
</StyledBody>
</StyledTable>
</TableWrapper>
</Wrapper>
);
}
I solve with this
const StyledHead = withStyle(BaseStyledHead, () => ({
width: "100%",
"#media only screen and (max-width: 767px)": {
width: "1000px",
},
}));

(google-maps-react) Material-UI popover detail bubble won't follow map marker when map centers to marker (LatLng)

I'm building a map with map markers that show a detail bubble built with the Material-UI Popover component. My code centers the marker when it is clicked, but the detail bubble/popover remains in the spot over where the map marker was before it was centered.
Here is a pic of the detail bubble/Popover when the map marker is centered:
I already tried positioning the detail bubble/popover as such:
.popover {
position: element(#map-marker);
transform: translateY(-100%);
}
But it still behaves the same. I think the popover component
can't calculate the change in the positioning of the map marker because the change is dictated by lat/lng values for the center of the map. I just can't think of any way to circumvent this.
Here is the full code:
Map.js
class ShowsMap extends Component {
constructor(props) {
super(props);
this.state = {
detailBubbleAnchorEl: null // The currently selected marker that the popover anchors to
}
}
handleDetailClose = () => {
this.setState({
detailBubbleAnchorEl: null
})
}
handleMarkerClick = (event, lat, lng) => {
this.setState({
detailBubbleAnchorEl: event.currentTarget
})
// Set center coordinates of map to be those of the map marker (redux action)
this.props.setSearchCenter({ lat, lng })
}
renderMap = () => {
const { detailBubbleAnchorEl } = this.state;
const detailOpen = Boolean(detailBubbleAnchorEl);
const { viewport } = this.props.searchLocation;
const { zoom } = fitBounds(viewport, { width: 400, height: 600})
return (
<GoogleMapReact
yesIWantToUseGoogleMapApiInternals
bootstrapURLKeys={{ key: MYAPIKEY }}
defaultCenter={this.props.center}
defaultZoom={this.props.zoom}
zoom={zoom + 1}
center={this.props.searchLocation.center}
onGoogleApiLoaded={({ map, maps }) => this.handleApiLoaded(map, maps)}
>
{
showLocations.map((location, index) => {
const { lat, lng } = location;
return (
<div lat={lat} lng={lng} key={index}>
<MapMarker onClick={(event) => this.handleMarkerClick(event, lat, lng)} />
<DetailBubble
id="event"
open={detailOpen}
anchorEl={detailBubbleAnchorEl}
onClose={this.handleDetailClose}
/>
</div>
)
})
}
</GoogleMapReact>
)
}
render() {
return (
<div ref={map => this.map = map} style={{ width: '100%', height: '100%',}}>
{this.renderMap()}
</div>
);
}
DetailBubble.js
const DetailBubble = ({ classes, open, anchorEl, onClose, id }) => {
return(
<Popover
id={id}
classes={{ paper: classes.container}}
open={open}
anchorEl={anchorEl}
onClose={onClose}
anchorOrigin={{
vertical: 'top',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
>
</Popover>
)
}
const styles = theme => ({
container: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
width: '200px',
height: '150px'
}
});
MapMarker.js
const styles = theme => ({
markerContainer: {
position: 'absolute',
width: 35,
height: 35,
left: -35 / 2,
top: -35 / 2,
},
marker: {
fill: '#3f51b5',
'&:hover': {
fill: 'blue',
cursor: 'pointer'
}
}
})
function MapMarker({ classes, onClick }) {
return (
<div className={classes.markerContainer}>
<Marker onClick={onClick} className={classes.marker} width={30} height={30} />
</div>
)
}
Thanks in advance for your help!

Resources