,Chain react-spring animations on hover - reactjs

I'm trying to do a simple multi-step animation on react-spring: rotating an icon that goes to 10deg, then -10deg, and go back to 0deg. This animation will execute when the user hovers the icon.
The following snippet illustrates an animation with just the 1st step:
const [isBooped, setBooped] = useState(false);
const style = useSpring({
rotate: isBooped ? 10 : 0,
config: config.wobbly,
});
const onMouseEnter = useCallback(() => setBooped(true), []);
useEffect(() => {
if (!isBooped) {
return;
}
const timeoutId = window.setTimeout(() => {
setBooped(false);
}, 250);
return () => {
window.clearTimeout(timeoutId);
};
}, [isBooped]);
return (
<Button onMouseEnter={onMouseEnter}>
<Icon style={style} />
</Button>
);
I understand that to accepts an array to chain multiple animations like so:
to: [{ rotate: 10 }, { rotate: -10 }, { rotate: 0 }],
...but I do not know how to handle this along with the isBooped state.
Here's a sandbox with the code:
https://codesandbox.io/s/react-spring-test-o8fwm?file=/src/Notifications.js

This is a good opportunity to use the imperative API react-spring has to offer.
You can also cancel the animation mid flow if you want it to stop animating when someone is not hovering over the icon, or maybe it should just reset itself back to 0 cutting the animation short (this is commented out in the example provided below).
By passing a function to the useSpring hook you get an array where the first item is your styles and the second is the API. You can then use an effect to trigger the async animation to begin running. Here's your codesandbox modified to demonstrate this: https://codesandbox.io/s/react-spring-test-forked-ildtb?file=/src/Notifications.js

Related

How can I stop my google map reloading every time I try to do something else off the map?

I have React Page. I splitted the page in 2. In the first half I have a form with some fields, and In the second half I have a Google Map Component. I added a Polyline. The path of the polyline gets updated every time the user left clicks (add point) or right clicks (remove point), or if it's dragged. Everything works well until I leave the map and focus on inputs or press some buttons in the other half. Then if I try to add new points or remove them ( this is not working because it has no points to remove) it starts a new polyline.
My methods are very simple, I took them from the docs and they do the job.
const addLatLng = (event) => {
const path = poly.getPath();
path.push(event.latLng);
}
const removeVertex = (vertex) => {
let path = poly.getPath();
path.removeAt(vertex)
}
// eslint-disable-next-line no-undef
google.maps.event.addListener(poly, 'rightclick', function (event) {
if (event.vertex === undefined) {
return;
} else {
removeVertex(event.vertex)
}
})
<div style={{ height: '100vh' }}>
<GoogleMap
center={center}
zoom={3}
onLoad={(map) => setMap(map)}
mapContainerStyle={{ width: '100%', height: '100%' }}
onClick={(event) => { !showInputForLink && addLatLng(event) }}
>
</GoogleMap>
</div>
First methods do the action and this is how I declared the map.
const { isLoaded } = useJsApiLoader({
googleMapsApiKey: ct.MAPS_API_KEY,
libraries: ['drawing, places']
})
// eslint-disable-next-line no-undef
const [map, setMap] = useState(/** #type google.maps.Map */(null))
const poly = new google.maps.Polyline({
strokeColor: '#160BB9',
strokeOpacity: 1.0,
strokeWeight: 3,
editable: true,
});
poly.setMap(map)
And this is how I declared the poly.
I tried to look up in Docs to see if I missed something, but I couldn't find anything that will not lose the focus on the map when I do something else or to start another polyline.
I am not using different components, everything is in on file.
Should I declare a Poly component inside the map? and not use the traditional JavaScript method?
How can I create this without resetting the map when I do actions in the first half?

Should I be using useEffect to animate on prop value change with React Native Reanimated?

I need a view to animate in to place when the value of some props change using Reanimated in React Native
I've only been able to find animating on, for example, a user tap using an onPress function to change the value of useSharedValue values. But I can't seem to find how to animate when props change. So currently I'm using useEffect.
export const SomeComponent = ({
top,
left
}) => {
const animation = useSharedValue({
top,
left
});
useEffect(() => {
animation.value = { top, left };
}, [top, left]);
const animationStyle = useAnimatedStyle(() => {
return {
top: withTiming(animation.value.top, {
duration: 1000
}),
left: withTiming(animation.value.left, {
duration: 1000
})
};
});
}
In the above code the props can change value, should I be triggering the animation with useEffect like I am currently? Or is there a better way to do it using just Reanimated.
Interesting. This works? I haven't seen it done this way.
Yes, useEffect is a good way to trigger animations. Usually I would do it like this:
const duration = 1000;
export const SomeComponent = ({
top: topProp,
left: leftProp
}) => {
const top = useSharedValue(topProp);
const left = useSharedValue(leftProp);
useEffect(() => {
top.value = withTiming(topProp, { duration });
left.value = withTiming(leftProp, { duration });
}, [topProp, leftProp]);
const animationStyle = useAnimatedStyle(() => ({
top: top.value,
left: left.value,
}));
return <Animated.View style={animationStyle}>
//...
}
The values are split up so that they're primitives. I don't know if this affects performance - I've never used an object as a shared value before.
The styles come straight from the shared values.
The useEffect waits for props to change and then runs the animations.
I've never tried running the animations within the styles - it seems like it shouldn't work to me, but I could be wrong!

How do I disable a hover animation until my component's intro animation is complete in ReactJS?

I'm using a useEffect for my component's intro animation and for my hover animation I'm using whileHover on my component. So essentially I want whilehover disabled until the useEffect is complete.
What I've tried:
const App = () => {
const [animationComplete, setAnimationComplete] = useState(false);
useEffect (() => {
//gsap.to animation....
setAnimationComplete(true)
},[]);
return (
<div
className="myComponent"
whileHover={animationComplete ? { scale: 1.1 } : {}}
I've also tried to add the following to whileHover:
whileHover={animationComplete === true ? { scale: 1.1 } : ""}
The hover animation still plays if the user hover's on the component while the intro animation is playing

Moving slider with Cypress

I've got a Slider component from rc-slider and I need Cypress to set the value of it.
<Slider
min={5000}
max={40000}
step={500}
value={this.state.input.amount}
defaultValue={this.state.input.amount}
className="sliderBorrow"
onChange={(value) => this.updateInput("amount",value)}
data-cy={"input-slider"}
/>
This is my Cypress code:
it.only("Changing slider", () => {
cy.visit("/");
cy.get(".sliderBorrow")
.invoke("val", 23000)
.trigger("change")
.click({ force: true })
});
What I've tried so far does not work.
Starting point of slider is 20000, and after test runs it goes to 22000, no matter what value I pass, any number range.
Looks like it used to work before, How do interact correctly with a range input (slider) in Cypress? but not anymore.
The answer is very and very simple. I found the solution coincidentally pressing enter key for my another test(date picker) and realized that pressing left or right arrow keys works for slider.
You can achieve the same result using props as well. The only thing you need to do is to add this dependency: cypress-react-selector and following instructions here: cypress-react-selector
Example of using {rightarrow}
it("using arrow keys", () => {
cy.visit("localhost:3000");
const currentValue = 20000;
const targetValue = 35000;
const increment = 500;
const steps = (targetValue - currentValue) / increment;
const arrows = '{rightarrow}'.repeat(steps);
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 20000)
.type(arrows)
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 35000)
})
#darkseid's answer helped guide me reach an optimal solution.
There are two steps
Click the slider's circle, to move the current focus on the slider.
Press the keyboard arrow buttons to reach your desired value.
My slider jumps between values on the sliders, therefore this method would work. (I am using Ion range slider)
This method doesn't require any additional depedency.
// Move the focus to slider, by clicking on the slider's circle element
cy.get(".irs-handle.single").click({ multiple: true, force: true });
// Press right arrow two times
cy.get(".irs-handle.single").type(
"{rightarrow}{rightarrow}"
);
You might be able to tackle this using Application actions, provided you are able to modify the app source code slightly.
Application actions give the test a hook into the app that can be used to modify the internal state of the app.
I tested it with a Function component exposing setValue from the useState() hook.
You have used a Class component, so I guess you would expose this.updateInput() instead, something like
if (window.Cypress) {
window.app = { updateInput: this.updateInput };
}
App: index.js
import React, { useState } from 'react';
import { render } from 'react-dom';
import './style.css';
import Slider from 'rc-slider';
import 'rc-slider/assets/index.css';
function App() {
const [value, setValue] = useState(20000);
// Expose the setValue() method so that Cypress can set the app state
if (window.Cypress) {
window.app = { setValue };
}
return (
<div className="App">
<Slider
min={5000}
max={40000}
step={500}
value={value}
defaultValue={value}
className="sliderBorrow"
onChange={val => setValue(val)}
data-cy={"input-slider"}
/>
<div style={{ marginTop: 40 }}><b>Selected Value: </b>{value}</div>
</div>
);
}
render(<App />, document.getElementById('root'));
Test: slider.spec.js
The easiest way I found assert the value in the test is to use the aria-valuenow attribute of the slider handle, but you may have another way of testing that the value has visibly changed on the page.
describe('Slider', () => {
it("Changing slider", () => {
cy.visit("localhost:3000");
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 20000)
cy.window().then(win => {
win.app.setValue(35000);
})
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 35000)
})
})
For whoever comes across this with Material UI/MUI 5+ Sliders:
First off, this github issue and comment might be useful: https://github.com/cypress-io/cypress/issues/1570#issuecomment-606445818.
I tried changing the value by accessing the input with type range that is used underneath in the slider, but for me that did not do the trick.
My solution with MUI 5+ Slider:
<Slider
disabled={false}
step={5}
marks
data-cy="control-percentage"
name="control-percentage"
defaultValue={0}
onChange={(event, newValue) =>
//Handle change
}
/>
What is important here is the enabled marks property. This allowed me to just click straight on the marks in the cypress test, which of course can also be abstracted to a support function.
cy.get('[data-cy=control-percentage]').within(() => {
// index 11 represents 55 in this case, depending on your step setting.
cy.get('span[data-index=11]').click();
});
I got this to work with the popular react-easy-swipe:
cy.get('[data-cy=week-picker-swipe-container]')
.trigger('touchstart', {
touches: [{ pageY: 0, pageX: 0 }]
})
.trigger('touchmove', {
touches: [{ pageY: 0, pageX: -30 }]
})

Snapping to position onDragEnd with motionValues using Framer Motion and React

I'm using framer motion to create a swipe interaction in my project. I'm trying to make it so that when the user is done dragging the child, it will 'snap' back into a set position.
I've seen from the docs that you can use a spring to animate a motion value: const y = useSpring(x, { damping: 10 }), but I guess I'm not doing it correctly? Heres my code:
export default function SwipeContainer(props) {
const x = useMotionValue(0);
const m = useSpring(x, { damping: 10 });
const handleDragEnd = (evt) => {
console.log(evt);
m.set(200);
}
return (
<div className={styles.swipeContainer}>
<motion.div
style= {{ x, m }}
className={styles.motionDiv}
drag="x"
onDragEnd={handleDragEnd}
>
{props.children}
</motion.div>
</div>
);
}
I'm expecting that when the dragEnd event happens, the child will animate to x:200, but thats not happening. Am I setting the value incorrectly, or perhaps its how I'm applying the motion values to the motion.div?
I didn't experiment with useSpring yet, but you can get it to work with useAnimation.
Here's a CodeSandbox with a similar situation: https://codesandbox.io/s/framer-motion-bottom-sheet-fixed-m2vls.
Hope this helps!

Resources