Change attribute from object using requestAnimationFrame in React - reactjs

I trying to update human.x using requestAnimationFrame and useState. I want to add elements called "humans" and use requestAnimationFrame to update the position to humans walk in "world" (using css properties). The method addHumans work but requestAnimationFrame update and empty my list.
World.tsx
import { useState, useEffect } from 'react'
import Human from './Human'
import IHuman from './Human'
type IHuman = {
id: number;
x: number;
y: number;
color: string;
}
export default function World() {
const [humans, setHumans] = useState<IHuman[]>([])
const addHuman = () => {
setHumans([...humans, {id: Math.random(), x: 0, y: 0, color: 'red'}])
}
const animate = () => {
setHumans(humans?.map((human, i) => {
return {...human, x: human.x + 1}
}));
// console.log(humans)
requestAnimationFrame(animate);
}
useEffect(() => {
requestAnimationFrame(animate);
}, []); // run once
return (
<>
<button onClick={addHuman}>new human</button>
<main>
{humans?.map(human => {
return <Human key = {human.id} x = {human.x} y = {human.y} color = {human.color} />
})}
</main>
</>
)
}
Human.tsx
export default function Human(props: any) {
return (
<div key={props.id} className="human" style={{ top: props.y + 'px', left: props.x + 'px', backgroundColor: props.color }}>{props.id}</div>
)
}

This is called a stale state. Update your code like this:
setHumans(humans => humans?.map((human, i) => {
return {...human, x: human.x + 1}
}));

Related

re-position react-rnd elements with a css transition

I have a list of two elements scenes rendered as <Rnd /> components from "react-rnd". What I'd like to happen is when I drag one element close to another one they should swap positions, in the list and in the UI. so [first, second] becomes [second, first]. The <Rnd /> position is controlled, this is the code I'm using:
import { useState } from "react";
import "./styles.css";
import { Rnd, Position, DraggableData } from "react-rnd";
interface IScene {
id: string;
name: string;
position: Position;
}
function Scene({
scene,
activeScene,
setActiveScene,
onDrag
}: {
scene: IScene;
activeScene: string;
setActiveScene: (id: string) => void;
onDrag: (d: DraggableData) => void;
}) {
const [dragged, setDragged] = useState(false);
return (
<Rnd
default={{
x: 0,
y: 0,
width: "200px",
height: "100px"
}}
position={scene.position}
onDragStart={() => setDragged(true)}
onDragStop={() => setDragged(false)}
onDrag={(_, d) => onDrag(d)}
onMouseDown={() => setActiveScene(scene.id)}
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "gray",
transition: dragged ? "" : "transform 0.5s",
border: "1px solid",
borderColor: activeScene === scene.id ? "white" : "gray",
zIndex: activeScene === scene.id ? "1" : "0"
}}
>
{scene.name}
</Rnd>
);
}
const initialScenesState = [
{
id: "1",
name: "first",
position: {
x: 0,
y: 0
}
},
{
id: "2",
name: "second",
position: {
x: 200,
y: 0
}
}
];
export default function App() {
const [scenes, setScenes] = useState<IScene[]>(initialScenesState);
const [activeScene, setActiveScene] = useState<string>("");
const handleStackScenes = () => {
setScenes((scenes) => {
let currentPosition = 0;
return scenes.map((scene) => {
const result = {
...scene,
position: {
x: currentPosition,
y: 0
}
};
currentPosition += 200;
return result;
});
});
};
const swapScenes = (first: IScene, second: IScene) => {
setScenes((scenes) => {
return scenes.map((scene) => {
if (scene.id === first.id) {
return second;
} else if (scene.id === second.id) {
return first;
} else return scene;
});
});
handleStackScenes();
};
const handleDrag = (scene: IScene) => (d: DraggableData) => {
console.log(d.x);
for (let i = 0; i < scenes.length; i++) {
if (
Math.abs(scenes[i].position.x - d.x) < 30 &&
scenes[i].id !== scene.id
) {
swapScenes(scene, scenes[i]);
}
}
};
console.log(scenes);
return (
<div className="App">
{scenes.map((scene) => (
<Scene
key={scene.id}
scene={scene}
activeScene={activeScene}
setActiveScene={setActiveScene}
onDrag={handleDrag(scene)}
/>
))}
</div>
);
}
The problem that I'm facing with my code, is that when I drag the left element to the right one, the swap happens exactly how I wanted, in the other way around the swap happens but I don't see a transition effect, when I checked what happens on the console, it seems that on the second case, the dom elements don't swap, but just the content and it the first case the actual dom elements move. What am I doing wrong?
EDIT: CodeSandBox

How to Convert a Class Component to a Functional Component in React?

I had never used React Class Components And Want to shoot confetti when some event happened. I'm confused with Class Component. Hope You can help with this issue. tried by creating arrow functions and removing this keyword .But I can't understand how to transform getInstance() function even why it is there?
import React, { Component } from 'react';
import ReactCanvasConfetti from 'react-canvas-confetti';
export default class Confetti extends Component {
getInstance = (instance) => {
// saving the instance to an internal property
this.confetti = instance;
}
onClickDefault = () => {
// starting the animation
this.confetti();
}
onClickCustom = () => {
// starting the animation with custom settings
this.confetti({ particleCount: Math.ceil(Math.random() * 1000), spread: 180 });
}
onClickCallback = () => {
// calling console.log after the animation ends
this.confetti().then(() => {
console.log('do something after animation');
});
}
onClickReset = () => {
// cleaning the canvas
this.confetti.reset();
}
render() {
const style = {
position: 'fixed',
width: '100%',
height: '100%',
zIndex: -1
};
const stylediv = {
position: 'fixed',
width: '100%',
height: '100%',
zIndex: 300000
};
return (
<>
<div style={stylediv}>
<ReactCanvasConfetti
// set the styles as for a usual react component
style={style}
// set the class name as for a usual react component
className={'yourClassName'}
// set the callback for getting instance. The callback will be called after initialization ReactCanvasConfetti component
refConfetti={this.getInstance}
/>
<button onClick={this.onClickDefault}>Fire with default</button>
<button onClick={this.onClickCustom}>Fire with custom</button>
<button onClick={this.onClickCallback}>Fire with callback</button>
<button onClick={this.onClickReset}>Reset</button>
</div>
</>
);
}
}
I'm trying to create Functional Component of the above Class Component
import React, { useRef } from 'react';
import ReactCanvasConfetti from 'react-canvas-confetti';
const Confetti = () => {
const Ref = useRef()
const getInstance = (instance) => {
if (Ref.current) {
Ref.current.confetti = instance
}
}
const onClickDefault = () => {
Ref.current.confetti();
}
const onClickCustom = () => {
Ref.current.confetti({ particleCount: Math.ceil(Math.random() * 1000),
spread: 180 });
}
const onClickCallback = () => {
Ref.current.confetti().then(() => {
console.log('do something after animation');
});
}
const onClickReset = () => {
Ref.current.confetti.reset();
}
const style = {
position: 'fixed',
width: '100%',
height: '100%',
zIndex: -1
};
const stylediv = {
position: 'fixed',
width: '100%',
height: '100%',
zIndex: 300000
};
return (
<>
<div ref={Ref} style={stylediv}>
<ReactCanvasConfetti
style={style}
className={'yourClassName'}
refConfetti={getInstance}
/>
<button onClick={onClickDefault}>Fire with default</button>
<button onClick={onClickCustom}>Fire with custom</button>
<button onClick={onClickCallback}>Fire with callback</button>
<button onClick={onClickReset}>Reset</button>
</div>
</>
);
}
export default Confetti

How to refactor React Leaflet GeoJSON component to use the useMap function with Next.js?

I'm working on a React-Leaflet application using Next.js and I'm struggling to refactor my GeoJSON component to be able to implement a 'zoom to feature' event using the useMap function. The React-Leaflet documentation states that this must be used in a descendant of the MapContainer per this other SO post, however my attempt to follow the example in the post results in no layer being added to the map but without any errors. I think this might be related to how the data props are passsed to the Map component itself, but being new to React/Next, I'm not sure where the error is, or what a better way to approach this is.
My current implementation has the Next.js Map page load static GeoJSON from a file via GetServerSideProps, which passes the props with points to the Map component itself. Where I'm struggling is how to refactor the Map component and the React-Leaflet GeoJSON component.
import { useRef } from 'react';
import { MapContainer, TileLayer, GeoJSON, useMap } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
const Map = ({points}) => {
const markerOptions = {radius: 2, weight: 1, opacity: 1, fillOpacity: 0.8, }
const markerStyles = function(feature) {
switch (feature.properties.type) {
case 'Sticker': return {color: '#a50026'};
case 'Mural': return {color: '#d73027'};
case 'Marker': return {color: '#f46d43'};
case 'Characters': return {color: '#fdae61'};
case 'Letters': return {color: '#fee090' };
case 'Tippex': return {color: '#ffffbf'};
case 'Spray': return {color: '#e0f3f8'};
case 'Chalk': return{color: '#abd9e9'};
case 'Label maker sticker': return{color: '#74add1' };
case 'Poster': return{color: '#4575b4'};
}
}
// Map Events
const geoJsonRef = useRef();
const onMouseOut = (e) => {
var layer = e.target;
geoJsonRef.current.setStyle(markerOptions);
}
const onMouseOver = (e) => {
var layer = e.target;
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7,
radius: 3
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
}
function onEachFeature(feature, layer){
if(feature.properties){
layer.bindPopup("<div class='popupImage'</div><img src=" + "https://d2qr25zh4rluwu.cloudfront.net/" + encodeURI(feature.properties.filename) + ".jpg " + "alt='peng spot photo'" + "height='200px'" + " " + ">" + "<div>" + "Type: " + feature.properties.type + "</div><div>" + "Description: " + feature.properties.desc + " </div>")
}
layer.on({
mouseover: onMouseOver,
mouseout: onMouseOut,
});
}
function pointToLayer(feature, latLng){
return L.circleMarker(latLng, markerOptions)
}
return (
<>
<MapContainer center={[50.1109, 8.6821]} zoom={14} scrollWheelZoom={false} style={{height: "100%", width: "100%"}} renderer={L.canvas()}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors"
/>
<GeoJSON data={points} pointToLayer={pointToLayer} pathOptions={markerStyles} onEachFeature={onEachFeature} ref={geoJsonRef} />
</MapContainer>
</>
)
}
export default Map
Per the mentioned SO post, I've tried to refactor the GeoJSON component code into a separate fuctional component to be added to the map container but this doesn't seem to work as the layer isn't added to the map. As this happens with no errors, I'm not sure what the issue is. Here is my first attempt at the refactor:
import { useRef } from 'react';
import { MapContainer, TileLayer, GeoJSON, useMap } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
const Map = () => {
return (
<>
<MapContainer center={[50.1109, 8.6821]} zoom={14} scrollWheelZoom={false} style={{height: "100%", width: "100%"}} renderer={L.canvas()}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors"
/>
<MapContent />
</MapContainer>
</>
)
}
const MapContent = ({points}) =>{
const map = useMap();
const zoomToFeature = (e) => {
const latLngs = [e.target.getLatLng()];
const markerBounds = L.latLngBounds(latLngs);
map.fitBounds(markerBounds);
}
const markerOptions = {radius: 2, weight: 1, opacity: 1, fillOpacity: 0.8, }
const markerStyles = function(feature) {
switch (feature.properties.type) {
case 'Sticker': return {color: '#a50026'};
case 'Mural': return {color: '#d73027'};
case 'Marker': return {color: '#f46d43'};
case 'Characters': return {color: '#fdae61'};
case 'Letters': return {color: '#fee090' };
case 'Tippex': return {color: '#ffffbf'};
case 'Spray': return {color: '#e0f3f8'};
case 'Chalk': return{color: '#abd9e9'};
case 'Label maker sticker': return{color: '#74add1' };
case 'Poster': return{color: '#4575b4'};
}
}
// Map Events
const geoJsonRef = useRef();
const onMouseOut = (e) => {
var layer = e.target;
geoJsonRef.current.setStyle(markerOptions);
}
const onMouseOver = (e) => {
var layer = e.target;
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7,
radius: 3
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
}
function onEachFeature(feature, layer){
if(feature.properties){
layer.bindPopup("<div class='popupImage'</div><img src=" + "https://d2qr25zh4rluwu.cloudfront.net/" + encodeURI(feature.properties.filename) + ".jpg " + "alt='peng spot photo'" + "height='200px'" + " " + ">" + "<div>" + "Type: " + feature.properties.type + "</div><div>" + "Description: " + feature.properties.desc + " </div>")
}
layer.on({
mouseover: onMouseOver,
mouseout: onMouseOut,
click: zoomToFeature
});
}
function pointToLayer(feature, latLng){
return L.circleMarker(latLng, markerOptions)
}
return(
<GeoJSON data={points} pointToLayer={pointToLayer} pathOptions={markerStyles} onEachFeature={onEachFeature} ref={geoJsonRef} />
)
}
export default Map
As above, I don't know why this isn't working but I expect it's with how I'm trying to pass the data props to the GeoJSON layer. Being relatively new to Next/React I'm still fuzzy on accessing and passing the data around.
So, what worked was pulling the GeoJSON into a separate component with a data prop like so:
import { useRef } from 'react'
import { GeoJSON, useMap } from 'react-leaflet';
const GeoJSONLayer = ({ data }) =>{
const map = useMap();
const zoomToFeature = (e) => {
const latLngs = [e.target.getLatLng()];
const markerBounds = L.latLngBounds(latLngs);
map.fitBounds(markerBounds);
}
const markerOptions = {radius: 2, weight: 1, opacity: 1, fillOpacity: 0.8, }
const markerStyles = function(feature) {
switch (feature.properties.type) {
case 'Sticker': return {color: '#a50026'};
case 'Mural': return {color: '#d73027'};
case 'Marker': return {color: '#f46d43'};
case 'Characters': return {color: '#fdae61'};
case 'Letters': return {color: '#fee090' };
case 'Tippex': return {color: '#ffffbf'};
case 'Spray': return {color: '#e0f3f8'};
case 'Chalk': return{color: '#abd9e9'};
case 'Label maker sticker': return{color: '#74add1' };
case 'Poster': return{color: '#4575b4'};
}
}
// Map Events
const geoJsonRef = useRef();
const onMouseOut = (e) => {
var layer = e.target;
geoJsonRef.current.setStyle(markerOptions);
}
const onMouseOver = (e) => {
var layer = e.target;
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7,
radius: 3
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
}
function onEachFeature(feature, layer){
if(feature.properties){
layer.bindPopup("<div class='popupImage'</div><img src=" + "https://d2qr25zh4rluwu.cloudfront.net/" + encodeURI(feature.properties.filename) + ".jpg " + "alt='peng spot photo'" + "height='200px'" + " " + ">" + "<div>" + "Type: " + feature.properties.type + "</div><div>" + "Description: " + feature.properties.desc + " </div>")
}
layer.on({
mouseover: onMouseOver,
mouseout: onMouseOut,
click: zoomToFeature
});
}
function pointToLayer(feature, latLng){
return L.circleMarker(latLng, markerOptions)
}
return(
<GeoJSON data={data} pointToLayer={pointToLayer} pathOptions={markerStyles} onEachFeature={onEachFeature} ref={geoJsonRef} />
)
}
export default GeoJSONLayer
And then using the component like so in my map component:
<GeoJSONLayer data={points}/>

How to convern ANTD class-based component to react function-based component

Can anyone give a suggestion on how can I convert Ant Design class-based component into function-based? I am new to class-based, so it is pretty confusing for me to convert class components. Now, my application is a functional-based component. Any suggestion will be appreciated!
Transfer Component
import { Transfer, Button } from 'antd';
class App extends React.Component {
state = {
mockData: [],
targetKeys: [],
};
componentDidMount() {
this.getMock();
}
getMock = () => {
const targetKeys = [];
const mockData = [];
for (let i = 0; i < 20; i++) {
const data = {
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
chosen: Math.random() * 2 > 1,
};
if (data.chosen) {
targetKeys.push(data.key);
}
mockData.push(data);
}
this.setState({ mockData, targetKeys });
};
handleChange = targetKeys => {
this.setState({ targetKeys });
};
renderFooter = (props, { direction }) => {
if (direction === 'left') {
return (
<Button size="small" style={{ float: 'left', margin: 5 }} onClick={this.getMock}>
Left button reload
</Button>
);
}
return (
<Button size="small" style={{ float: 'right', margin: 5 }} onClick={this.getMock}>
Right button reload
</Button>
);
};
render() {
return (
<Transfer
dataSource={this.state.mockData}
showSearch
listStyle={{
width: 250,
height: 300,
}}
operations={['to right', 'to left']}
targetKeys={this.state.targetKeys}
onChange={this.handleChange}
render={item => `${item.title}-${item.description}`}
footer={this.renderFooter}
/>
);
}
}
ReactDOM.render(<App />, mountNode);
in function component to define state you can use useState hook, and also instead of lifecycles such as componentDidMount, componentDidUpdate, ... you can use useEffect hook, liek this:
import {useState, useEffect} from 'react';
import { Transfer, Button } from 'antd';
function App() {
const [state, setState] = useState({
mockData: [],
targetKeys: [],
});
const getMock = () => {
const targetKeys = [];
const mockData = [];
for (let i = 0; i < 20; i++) {
const data = {
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
chosen: Math.random() * 2 > 1,
};
if (data.chosen) {
targetKeys.push(data.key);
}
mockData.push(data);
}
setState({ mockData, targetKeys });
};
useEffect(()=>{
getMock();
}, [])
const handleChange = targetKeys => {
setState(prevState => ({ ...prevState, targetKeys }));
};
const renderFooter = (props, { direction }) => {
if (direction === 'left') {
return (
<Button size="small" style={{ float: 'left', margin: 5 }} onClick={getMock}>
Left button reload
</Button>
);
}
return (
<Button size="small" style={{ float: 'right', margin: 5 }} onClick={getMock}>
Right button reload
</Button>
);
};
return (
<Transfer
dataSource={state.mockData}
showSearch
listStyle={{
width: 250,
height: 300,
}}
operations={['to right', 'to left']}
targetKeys={state.targetKeys}
onChange={handleChange}
render={item => `${item.title}-${item.description}`}
footer={renderFooter}
/>
);
}

react-native: How to create custom component dynamically and destroy it after use?

I am creating a screen in functional component where I have to execute an animation when there is any event occurs ... This event could occur 1000 times when screen is open ... so I have implemented a custom component which takes position on screen and animates ....
const FloatingComponent = (props) => {
const animationView = useSharedValue(1)
const animationOpacityView = useSharedValue(1)
const animationViewStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateY: withTiming(animationView.value, {
duration: 3500
}),
}
],
opacity: withTiming(animationOpacityView.value, {
duration: 1500
})
}
})
useEffect(() => {
console.log('Component animation called')
animationView.value = -((Screen.width * 0.25))
animationOpacityView.value = 0
});
return (
<Animated.View style={[Styles.handImg, { top: props.topDistance }, animationViewStyle]}>
<Image
style={Styles.handImage}
source={require('../../../assets/images/hand.png')}
/>
</Animated.View>
);
};
To create it dynamically I implemented it like this
const driverFactory = (itemNumber) => {
console.log(itemNumber)
return (<FloatingComponent id={1} topDistance={(Screen.width * 0.25) * itemNumber} />);
};
but it never show up and executes ....
while if I add this
<FloatingDriver id={5} topDistance={(Screen.width * 0.25) * 6} />
to main screen return it always executes .... but by these I can not create n number of components at any time when i receive notification ...
I would have a parent component manage these n child components and their rendering. Here is an example showing how one might render all the components with animations and handle showing/removing them as needed.
import React from 'react';
import {Animated, Button, SafeAreaView, StyleSheet} from 'react-native';
const Particle = ({particle: {x, y}, onFinish}) => {
const opacity = React.useRef(new Animated.Value(0)).current;
React.useEffect(() => {
Animated.timing(opacity, {
toValue: 1,
duration: 2000,
useNativeDriver: true,
}).start(() => {
onFinish();
});
}, []);
return (
<Animated.View
style={{
position: 'absolute',
left: x - 10,
bottom: y - 10,
width: 20,
height: 20,
backgroundColor: 'yellow',
opacity,
}}
/>
);
};
const ParticleSystem = () => {
const [layoutRectangle, setLayoutRectangle] = React.useState();
const [particleList, setParticleList] = React.useState([]);
const currentRef = React.useRef();
const addRandomParticle = React.useCallback(() => {
if (layoutRectangle) {
const newParticle = {
id: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER),
x: Math.random() * layoutRectangle.width,
y: Math.random() * layoutRectangle.height,
};
setParticleList([newParticle, ...particleList]);
}
}, [layoutRectangle, particleList]);
const removeParticle = React.useCallback(
(particle) => {
setParticleList(particleList.filter(({id}) => id !== particle.id));
},
[particleList],
);
currentRef.current = removeParticle;
return (
<SafeAreaView
style={styles.screen}
onLayout={(event) => setLayoutRectangle(event.nativeEvent.layout)}>
{particleList.map((particle) => (
<Particle
key={particle.id}
particle={particle}
onFinish={() => {
currentRef.current(particle);
}}
/>
))}
<Button title="Add particle" onPress={() => addRandomParticle()} />
</SafeAreaView>
);
};
export default ParticleSystem;
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: 'black',
},
});

Resources