React-spring Transition didn't repeat animation - reactjs

In my react app I have component, where I using Transition from react-spring.
import { Transition } from 'react-spring'
export class myComponent {
state = {
items: []
}
static getDerivedStateFromProps(props, state){
const newItems = [...props.items]
if(props.items !== state.items){
return {items: newItems}
}
}
render() {
const { items } = this.state
<Transition
items={items}
keys={item => items.id}
initial={{ opacity: 1, height: 'auto' }}
from={{ opacity: 0, height: 0 }}
enter={{ opacity: 1, height: 'auto' }}
leave={{ opacity: 0, height: 0 }}
>
{item => propsAnimations => (
<section style={propsAnimations}>
<Item
key={item.selectionId}
/>
</section>
)}
</Transition>
}
}
But when I updating this.state.items this is didn't repeat animation.
When I updating items in state, I change only elements order in array.

Related

How do I transition from one image to another by changing state in React-Native

I have two images with little difference between them and each corresponding to a particular state. I need to smoothly transition from one to the other when I change the state so that the effect feels like only the part which is different in the two images has undergone animation,the rest of the image staying as it is.
I want it to work so that when I render the second image on stateChange, only the rod like part in the second image appears to fade in,the rest remaining still.
I think this can be achieved without using any animation libraries like react-transition-group probably by using some of the life cycle methods in React and obviously, the AnimatedAPI. The major issue that I am facing is that when I update the state I have no control over the previous image that was rendered. I somehow want the previously rendered image to stay until newly rendered Component appears and does its animation. Here is what I tried to do. I have this ImageLoader Component which renders an image while providing a fading-in animation to it.
class ImageLoader extends React.Component {
constructor(){
super()
this.opacity= new Animated.Value(0)
}
componentDidUpdate(){
{this.onLoad()}
}
onLoad = () => {
this.opacity.setValue(0);
Animated.timing(this.opacity, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
}
render() {
return (
<Animated.Image onLoad={this.onLoad}{...this.props}style={[
{opacity: this.opacity,}, this.props.style,
]}
/>
);
}
}
export default class App extends React.Component {
state={
no:1,
}
render() {
let Dun=()=>{return this.state.no==1?
<ImageLoader source={require('./assets/img1.PNG')}/>: <ImageLoader
source={require('./assets/img2.PNG')}/>
}
const calc=()=>{
this.setState((state)=>({no:Math.abs(state.no-1)}));
}
return (
<View style={styles.container}>
<View style={{height:100,marginLeft:50}}>
{Dun()}
<Button onPress={()=>{calc()}}> Press</Button>
</View>
</View>
);
}
}
You could use 2 animated images to give the impression that one is fading into the other. Here is a solution based on your example:
import React from 'react';
import { Animated, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import images from 'src/images';
const styles = StyleSheet.create({
image: {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0
}
});
class ImageSwitcher extends React.Component {
fadeInOpacity = new Animated.Value(0);
fadeOutOpacity = new Animated.Value(1);
state = {
prevSource: null
};
componentDidMount() {
this.onLoad();
}
componentDidUpdate() {
this.onLoad();
}
componentWillReceiveProps({ source: newSource }) {
const { source } = this.props;
if (newSource !== source) {
this.setState({ prevSource: source });
}
}
onLoad = () => {
this.fadeInOpacity.setValue(0);
this.fadeOutOpacity.setValue(1);
Animated.timing(this.fadeInOpacity, {
toValue: 1,
duration: 500,
useNativeDriver: true
}).start();
Animated.timing(this.fadeOutOpacity, {
toValue: 0,
duration: 500,
useNativeDriver: true
}).start();
};
render() {
const { prevSource } = this.state;
return (
<View
style={{
width: 200,
height: 200
}}
>
<Animated.Image {...this.props} style={[styles.image, { opacity: this.fadeInOpacity }]} resizeMode="cover" />
{prevSource && (
<Animated.Image {...this.props} style={[styles.image, { opacity: this.fadeOutOpacity }]} resizeMode="cover" source={prevSource} />
)}
</View>
);
}
}
export default class App extends React.Component {
state = {
source: images.first
};
handleToggle = () => this.setState(({ source }) => ({ source: source === images.first ? images.second : images.first }));
render() {
const { source } = this.state;
return (
<View style={{ flex: 1 }}>
<ImageSwitcher source={source} />
<TouchableOpacity onPress={this.handleToggle}>
<Text>Toggle Image</Text>
</TouchableOpacity>
</View>
);
}
}
const SwitchImage = (props) => {
const [previousImage, setPreviousImage] = useState('');
const opacity = useRef(new Animated.Value(0)).current;
useEffect(()=>{
Animated.timing(opacity, {
toValue: 1,
duration: 300,
easing: Easing.spring,
useNativeDriver: true
}).start(()=>{
setPreviousImage(props.source);
opacity.setValue(0);
})
}, [props.source])
return(
<View style={{width: props.style.width, height: props.style.height}}>
<FastImage
resizeMode={props.resizeMode}
source={previousImage}
style={{width: props.style.width, height: props.style.height, position: 'absolute'}}
/>
<Animated.View style={{opacity: opacity}}>
<FastImage
resizeMode={props.resizeMode}
source={props.source}
style={{width: props.style.width, height: props.style.height, position: 'absolute'}}
/>
</Animated.View>
</View>
)}

upgrade react-spring code that uses animate and transition to latest react-spring

I have this code that uses react-spring 4.0.1 code:
export const Nodes: React.FunctionComponent<NodesProps> = ({nodes, nodeClickHandler}) => {
return (
<Transition
native={true}
items={nodes}
keys={keyAccessor}
config={{ tension: 1000, friction: 130, mass: 5 }}
from={(node: HierarchyPointNode<GraphicalNode>) => {
const parentTopLeft = !!node.parent
? {left: node.parent.x, top: node.parent.y}
: {left: 0, top: 0};
return {
left: parentTopLeft.left,
opacity: 0,
top: parentTopLeft.top,
};
}}
enter={(node: HierarchyPointNode<GraphicalNode>) => {
return {
left: node.x,
opacity: 1,
top: node.y,
};
}}
update={(node: HierarchyPointNode<GraphicalNode>) => {
return {
left: node.x,
opacity: 1,
top: node.y,
};
}}
leave={(node: HierarchyPointNode<GraphicalNode>) => {
return {
left: node.x,
opacity: 0,
top: node.y,
};
}}
>
{nodes.map((node: HierarchyPointNode<GraphicalNode>) => (styles: CSSProperties) => {
const key = keyAccessor(node);
return (
<animated.g
className="cx-group"
style={{
cursor: "pointer",
pointerEvents: (styles.opacity as any).interpolate((v: any) => v < 0.5 ? "none" : "all") }
}
width={40}
height={20}
opacity={styles.opacity}
transform={template`translate(${styles.left}, ${styles.top})`}
key={keyAccessor(node)}
>
<Node node={node} nodeClickHandler={nodeClickHandler} key={key} />
</animated.g>
);
})}
</Transition>
);
};
In react-spring 8, there is no transition and animate.
How can I upgrade this code to the latest version.
In version 8 the renderProps api can be accessed from react-spring/renderprops. So you can try with the following import.
import {Transition, animated} from 'react-spring/renderprops';
You can also try the hooks api. I think the code is more simple, I love it.

(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!

Toggle Button Group in React

New to react and trying to use in a new electron learning project I have. I'm trying to build a really basic drawing app.
I created a ToolbarButton class. That would represent each button and a Toolbar that would manage the button group. Example If you pick a 'primary' tool then it should turn off all other primary tools and leave only your current selection.
In jQuery I'd just do something like
let button = $(this);
let toolbar = $(this).parent();
toolbar.find('button.toolbar-button').removeClass('active');
button.addClass('active');
How would I do the same in react? I can toggle what I want to with setState from within the ToggleButton but separating it out into a prop seems to be an answer, but then I need to have the Toolbar manage the button 'active' states and I'm not sure how to do that. Think the answer is in ReactDOM, super newbie to react so apologize if the answer is overly obvious.
import React from 'react';
import FontAwesome from 'react-fontawesome';
import { ButtonGroup } from 'react-bootstrap';
import { ChromePicker} from 'react-color';
class ToolbarButton extends React.Component
{
state =
{
active: false
}
handleClick = ()=> {
if(this.props.onClick)
{
this.props.onClick();
}
this.setState({ active: !this.state.active});
}
render(){
return <div className={`btn btn-primary${this.state.active ? ' active' : ''}`} onClick={this.handleClick}>{this.props.children}</div>
}
}
class ColorPickerButton extends React.Component
{
constructor(props)
{
super(props);
this.state = {
displayColorPicker: false,
color: { r: 255, g: 255, b: 255, a:1 }
}
}
state = {
flyout: 'bottom',
displayColorPicker: false,
color: { r: 255, g: 255, b: 255, a:1 }
}
/* This button should have an option to display how the colorpicker flys out */
static flyoutStyles =
{
normal: { },
top: {top: '0px' },
bottom: { top: '100%' },
left: { right: '100%' },
right: { left: '100%' }
}
handleClick = (e) => {
this.setState({ displayColorPicker: !this.state.displayColorPicker});
}
handleClose = () => {
this.setState({ displayColorPicker: false });
}
handleChange = (color) => {
this.setState({ color: color.rgb });
}
stopPropagation = (e) => {
e.stopPropagation();
}
render()
{
const swatchStyle = {
backgroundColor: `rgba(${this.state.color.r},
${this.state.color.g},
${this.state.color.b},
${this.state.color.a})`,
height: '16px',
width: '16px',
border: '1px solid white'
};
const popup = {
position: 'absolute',
zIndex: 2,
top: 'calc(100% + 2px)'
};
const cover = {
position: 'fixed',
top: '0px',
right: '0px',
left: '0px',
bottom: '0px',
};
return (
<ToolbarButton onClick={this.handleClick} active={this.state.displayColorPicker}>
<div style={swatchStyle} />
{
this.state.displayColorPicker ?
<div style={popup} onClick={this.stopPropagation}>
<div style={ cover } onClick={ this.handleClose }/>
<ChromePicker color={this.state.color} onChange={this.handleChange} />
</div>
: null
}
</ToolbarButton>
);
}
}
export class CanvasToolbar extends React.Component
{
handleClick = (e) => {
}
render(){
return (<div className="CanvasToolbar">
<ButtonGroup vertical>
<ToolbarButton>
<FontAwesome name='paint-brush' />
</ToolbarButton>
<ToolbarButton>
<FontAwesome name='eraser' />
</ToolbarButton>
<ToolbarButton>
<FontAwesome name='magic' />
</ToolbarButton>
<ColorPickerButton />
</ButtonGroup>
</div>);
}
}

How do I add an image to the DOM after another image has loaded?

I want to make sure images are loaded in the right order: first the primary image, then the secondary image. My plan is to inject the secondaryImage once the primary image is done.
class HoverImage extends Component {
constructor (props) {
super(props)
this.state = { secondaryImage: null }
}
primaryImageLoaded () {
//here I would like inject <img className='img-responsive' src={stripUrl(secondaryImage)} /> before primaryImage
}
render () {
const primaryImage = this.props.primaryImage
const secondaryImage = this.props.secondaryImage
if (secondaryImage) {
return (
<div style={{position: 'relative'}}>
<img
className='img-responsive'
src={stripUrl(primaryImage)}
onLoad={this.primaryImageLoaded.bind(this)}
style={{
':hover': {
opacity: 0
},
position: 'absolute',
top: 0}}
/>
</div>
)
}
}
other solutions that create the same effect are fine too!
Try this:
class HoverImage extends Component {
constructor (props) {
super(props)
this.state = {
secondaryImage: null,
showSecondaryImage: false,
}
}
primaryImageLoaded () {
this.setState({showSecondaryImage: true});
}
render () {
const primaryImage = this.props.primaryImage;
const secondaryImage = this.props.secondaryImage;
secondaryImage ?
return (
<div style={{position: 'relative'}}>
{this.state.showSecondaryImage ?
<img className='img-responsive' src={stripUrl(secondaryImage)} />
: <div/>}
<img
className='img-responsive'
src={stripUrl(primaryImage)}
onLoad={this.primaryImageLoaded.bind(this)}
style={{
':hover': {
opacity: 0
},
position: 'absolute',
top: 0
}}
/>
</div>
)
: return <div/>
}
}
jsfiddle link: http://jsfiddle.net/d7hwzapc/
class HoverImage extends Component {
constructor (props) {
super(props)
this.state = {
firstImageLoaded: false,
};
}
componentDidMount() {
this.setState({ firstImageLoaded: true });
}
loadSecondImage() {
if(this.state.firstImageLoaded) {
return (<img
className='img-responsive'
src={stripUrl(this.props.secondaryImage)}
/>);
}
}
render () {
return (
<div style={{position: 'relative'}}>
<img
className='img-responsive'
src={stripUrl(this.props.primaryImage)}
onLoad={this.setState({ firstImageLoaded: true })}
style={{
':hover': {
opacity: 0
},
position: 'absolute',
top: 0}}
/>
{this.loadSecondImage()}
</div>
)
}
When the initial mount is done, it will set a flag in the state which will trigger a re-render.
Hope that helps!
ps: this answer is in no way perfect but should get what you wanted done.

Resources