Prevent useState rerendering animation - javascript

I have set up a realtime firebase that detects user joining my site. When they join, I add them to my useState variable and then display them on the map. I use framer motion to create a simple animation.
The problem is that when a user gets added, removed, or modified that user gets animated, and all the other users get animated again as well. I have tried to use React.memo, with no success. Maybe I am just using it wrong or there is another solution that I can't come up with yet.
const [onlineUsers, setOnlineUsers] = useState([]);
{onlineUsers?.map((user) => (
<motion.g
initial={{ scale: 0 }}
animate={{ rotate: 0, scale: 1 }}
transition={{
type: 'spring',
stiffness: 260,
damping: 20,
}}
>
<image
width='12'
height='12'
href={UserImage}
/>
</motion.g>
))}

Framer motion detect animate from key attribute, set unique key to each iterated element.

How useState and useMemo works? Every time an element changes, it will re-render the whole component. As you are storing all users in onlineUsers, once you modified onlineUsers will re-render your variable (all elements inside your variable). Check this example

Related

How can I use react-zoom-pan-pinch to allow users to view all of a chart that changes size based on user input?

I've designed and built an interactive Org Chart that allows users to view business organizations in a hierarchical format. By default only the first row below the root node of the chart is visible, but users can click to expand the chart further, thereby changing the size of the chart. Users can also drag and drop nodes to simulate a reorganization of the business.
I'm currently using react-zoom-pan-pinch to allow users to zoom and pan the chart. It works very well when the org chart has not been expanded too much, but becomes problematic at larger chart scales.
The problem is that the organizations being represented by the chart are very broad in comparison to their depth, meaning a fully expanded chart is a horizontal rectangle, not a square. react-zoom-pan-pinch will only allow me to zoom out to the maximum vertical extent of the chart, meaning users can't view a fully expanded organization without scrolling from side to side. This is not an acceptable behavior.
This is for work, so I cannot post code without violating numerous agreements. Instead I have linked to the react-zoom-pan-pinch documentation and will go over what I have tried changing.
The first place I looked was the TransformWrapper Props section of the documentation.
There I found the inititalScale, minScale, and maxScale props.
I can set the initialScale prop to a value of less than 1, and obtain something close to the result I want at first. Setting it to 0.5 results in the chart being zoomed out further than normally possible, but when I zoom in to a value of 1 I am unable to zoom back out. This was expected, as the minScale prop was still set to 1.
Having checked that the props indeed work, I went ahead and set minScale to 0.5, assuming I would be able to zoom back out to the initial view seen when initialScale is set to 0.5. This seemed like it should work, but it did not. Even with the minScale prop set to 0.5, I am unable to zoom back out after zooming in to a value of 1. This is very strange to me, as the acceptance of 0.5 as the initialScale prop and subsequent rendering of the chart indicates that values below 1 are acceptable.
I am now messing around with the rest of the props listed in the documentation, but have yet to achieve the desired result (infinite zoomout).
I believe the root of the issue is that react-zoom-pan-pinch is meant for images, not things that change size and aspect ratio, but it is a good package and I would prefer to keep using it.
Is anyone familiar enough with this package to know the settings I should be using to allow infinite zoom out, and if so what are those settings?
I discovered the answer to my own question. It turns out the minScale, maxScale, and other props were not being passed to the component properly. Once they are passed properly the package works very well. Here's my explanation/fix
The documentation suggests doing this:
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
class Example extends Component {
render() {
return (
<TransformWrapper
initialScale={1}
minScale={0.5}
maxScale={7}
initialPositionX={200}
initialPositionY={100}
>
{({ zoomIn, zoomOut, resetTransform, ...rest }) => (
<React.Fragment>
<div className="tools">
<button onClick={() => zoomIn()}>+</button>
<button onClick={() => zoomOut()}>-</button>
<button onClick={() => resetTransform()}>x</button>
</div>
<TransformComponent>
<img src="image.jpg" alt="test" />
<div>Example text</div>
</TransformComponent>
</React.Fragment>
)}
</TransformWrapper>
);
}
}
The above doesn't work, and the minScale and maxScale props aren't passed to the component. If you open the React dev tools in your browser and go to TransformWrapper, you'll see the default values of 1 for minScale and 8 for maxScale, not the values you entered in your code.
You can solve the problem by creating an object:
const transformOptions = {
initialScale: 1,
minScale: 0.5,
maxScale: 2
}
Then setting an options prop inside the TransformWrapper component equal to the object, like so:
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
class Example extends Component {
render() {
return (
<TransformWrapper
initialScale={1}
options={transformOptions}
initialPositionX={200}
initialPositionY={100}
>
{({ zoomIn, zoomOut, resetTransform, ...rest }) => (
<React.Fragment>
<div className="tools">
<button onClick={() => zoomIn()}>+</button>
<button onClick={() => zoomOut()}>-</button>
<button onClick={() => resetTransform()}>x</button>
</div>
<TransformComponent>
<img src="image.jpg" alt="test" />
<div>Example text</div>
</TransformComponent>
</React.Fragment>
)}
</TransformWrapper>
);
}
}
The same thing applies to pan, wheel, and zoom options. They don't work if set directly in the component as suggested by the documentation, but do work if you create objects like I did above.

How to use animation multiple times in framer motion

Just like in the title, I'm trying to animate my component multiple times when clicking a button. Currently, animation only runs once and then it doesn't work anymore, how could I possibly implement that
here is an example of what I'm trying to do:
const controls = useAnimation();
const handleClick = (e) => {
controls.start({
rotate: "360deg",
});
};
return(
<motion.button onClick={handleClick}>Button</motion.button>
<motion.div className="someRect" animate={controls}></motion.div>
)
To be honest, I've never worked with useAnimation controls so I have no idea what's going on here and how to come around this obstacle using your approach, but you may achieve what you are after with something like this.
<button onClick={() => setRotate((prevState) => prevState + 360)}>
Button
</button>
<motion.div
className="someRect"
animate={{
rotate: rotate,
transition: { duration: 3 }
}}
style={{ width: "100px", height: "100px", background: "red" }}
>
</motion.div>
I know that this solution is not optimal as the state is growing and growing with each click to possibly very substantial size which may cause problems at some point.
Check my solution in this CodeSanbox
Hope maybe someone will come up with a much more elegant solution which I'd appreciate seeing too.

React Spring: dynamic values in useTransition() when using it alongside react-router

In this example you can see some nice transition between pages triggered by the route change. (took from LevelUp Tutorials' React Animation course, thanks a lot Scott Tolinski).
Now I'd like to make these transitions happen in both directions, depending on which page it's transitioning to (and from), for instance:
Page One -> Page Two (both pages transition from left to right)
Page Three -> Page One (both pages transition from right to left)
etc
From that first example I created this example where the value of x is dynamic and should be evaluated to either 100 or -100, based on the direction of the transition.
I haven't fundamentally understood how useTransition() works, and the documentation is rather limited. The examples look amazing but are quite hard to understand.
This example seems to do a similar thing to what I'm trying to achieve but the code feels like black magic: the y property of each object returned from rows.map() appears to be related to the y value on the functions assigned to enter and update properties because if I remove that I get the error: Cannot read property 'replace' of undefined. How does that work?
This problem has two part.
determining the direction
change the animation
I created an example for solving the second part. When the user click page one I reverse the animation.
const reverse = location.pathname === '/';
const transitions = useTransition(location, location => location.key, {
from: { opacity: 0, transform: `translate3d(${reverse ? '-100%' : '100%'},0,0)` },
enter: { opacity: 1, transform: "translate3d(0,0,0)" },
leave: { opacity: 0, transform: `translate3d(${reverse ? '100%' : '-100%'},0,0)` },
// "initial: null" should only disable the 'from' initial transition, not the subsequent 'leave' transition (on the first manually triggered transition)
initial: null
});
Here is the sandbox: https://codesandbox.io/s/spring-transition-with-routes-215t8
For the first part to determine when to reverse the animation, I would store the path at each click and compare the next one to the previous. There is an example of storing the path here: Check history previous location before goBack() react router v4
I hope it helps.

How to rebuild the iOS Picker animation in React Native

Is there a way in React Native to rebuild the iOS picker component completely in Java Script? I don't need the common picker, but a normal scroll view with a similar fade-out effect like the iOS picker.
EDIT – I think I have not explained my initial answer exactly enough. This is why I complete it here:
I want to build a scroll view that takes over the whole screen. It's not supposed to give the user the possibility to elect some item, like the the iOS Picker does. Nevertheless, it's supposed to be a 'normal' scroll view, that shows the user some information, e.g. different chats, tasks, news and so on.
The only difference to React Native's common scroll view should be the fade-out effect at the top: When the user scrolls the content up, it should not just leave the screen at its top edge, but it should use the iOS Picker's fade-out effect (see picture).
This fade-out effect is made up of two parts: First of all, it raises the content's transparency with a decreasing y-coordinate. Furthermore, this content seems to escape into the third dimension.
My problem is, that I don't see a way to achieve this three-dimensionality of the content in React Native. I've to add, that the content in my scroll view does not consist of small, equally sized items (like e.g. the texts 'Item 1', 'Item 2', 'Item 3',...), but of bigger items with different sizes like images or whole textboxes.
You can use this NPM module to get what you want. That module works the same in Android and iOS. Do not reinvent the wheel :)
EDIT: Now I've understood what you want. You can try this snack that I've made for you:
https://snack.expo.io/r1qnxSt9m
Of course you need to improve it, but it's a beginning.
You can achieve the desired effect with the Animated api. The idea is to set different input ranges to the items in your list. You then hook the opacity to the scroll value of your ScrollView (or any list component). I have simplified the code, but it should be enough to demonstrate the idea.
The example below only demonstrates an opacity effect, but you could easily add a translate effect to get the exact animation that you are looking for.
const data = []; // array that contains the text
const items = [];
for (let i = 0; i < data.length; ++i) {
const distanceFromViewCenter = Math.abs(i * ITEM_HEIGHT);
const inputRange = [
-distanceFromViewCenter - 2 * ITEM_HEIGHT,
-distanceFromViewCenter - ITEM_HEIGHT,
-distanceFromViewCenter, // Middle of picker
-distanceFromViewCenter + ITEM_HEIGHT,
-distanceFromViewCenter + 2 * ITEM_HEIGHT,
];
items.push(
<Animated.View
style={{
opacity: this._scrollValue.interpolate({
inputRange,
outputRange: [0.0, 0.3, 1.0, 0.3, 0.0],
}),
}}
>
<Text style={{ height: ITEM_HEIGHT }}>{data[i]}</Text>
</Animated.View>
)
}
<ScrollView
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this._scrollValue } } }],
{ useNativeDriver: true }
)}
>
{items}
</ScrollView>

Looping bubbles animation

I'm working on a react website but new to the animation area.
I'm trying to accomplish the effect like the hero section of this page: https://stripe.com/us/customers, where there's an infinite loop of circles scrolling from right to left, each with different images and sizes.
How should I get started with this infinite loop of objects animation using React? Is there some library that I can use, or is there a react code snippet sample that I can learn from?
you may be able to accomplish this using purely CSS (check out these crazy pure CSS animation examples that you can fork https://envato.com/blog/pure-css-animation-snippets/)
But the ReactJS approach would be to create a component like.. lets say FloatingIcon
import React from 'react';
class FloatingIcon extends React.Component {
constructor(props) {
super(props);
this.state{
horizontalPosition: "0px",
verticalPosition: "0px",
imgRef: "http://blah.com/asdf",
backgroundColor: "#000000"
}
}
changePosition(horizontalPosition, verticalPosition) {
this.setState({
horizontalPosition,
verticalPosition
});
}
render() {
return (
<div>
<img
href={this.state.imgRef}
style={
{translate(this.state.horizontalPosition,
this.state.verticalPosition)},
backgroungColor:{this.state.backgroundColor}}>
</img>
</div>
);
}
}
export default FloatingIcon;
each floating icon has an image, background-color, and position in it's state. Create as many as you need for your page and store them in an array. You can change the position using the changePosition function that sets the state and updates causing the DOM to render again. Getting it to float all pretty will take some work, but if you calculate correctly and create a good position change. This will work in a React technical sense and this is a React like design for such a problem creating components to accomplish these tasks using single responsibility principles. Let me know how it goes. Hope this helps friend.
Cheers!

Categories

Resources