Struggling to get framer-motion's exit animation working. [React.JS] - html

RenderPokemonInfo.js:
const RenderPokemonInfo = (props) => {
const pokemon = props.pokemonToUse;
const pokemonCurrentTypes = [];
let gradientString = "";
const animateFromTop = {
before: {
y: '-100vh',
opacity: 0,
},
onScreen: {
y: '0',
opacity: 1,
},
after: {
y: '100vh',
opacity: 0,
}
}
pokemon.types.map((typeData) => {
pokemonCurrentTypes.push(typeData.type.name);
})
if(pokemonCurrentTypes.length === 1) {
gradientString = getGradient({typeString: pokemonCurrentTypes[0]});
} else {
gradientString = getGradient({typeString: pokemonCurrentTypes[0]}) + ', ' + getGradient({typeString: pokemonCurrentTypes[1]});
}
return (
<>
<motion.div variants={animateFromTop} initial='before' animate='onScreen' exit='after' key={pokemon.name}>
<div style={{
position: 'absolute',
width: '420px',
height: '640px',
left: '75%',
top: '30px',
transform: 'translateX(-50%)',
backgroundImage: 'linear-gradient(225deg, ' + gradientString + ')',
boxShadow: '0 20px 20px 0 rgba(0,0,0,0.2)',
borderRadius: '30px'}} key={'poke-pic-' + pokemon.name}>
<fieldset className="poke-height-fieldset">
<legend style={{position: 'absolute', left: '10px'}}>{pokemon.height * 10} cm</legend>
<div className='big-poke-img-container'>
<img src={'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/dream-world/' + pokemon.id + '.svg'} className='big-poke-img'></img>
</div>
</fieldset>
<div className='poke-type-container'>
{pokemon.types.map((type) => <PokemonTypeButton typeString={type.type.name} isButton={false} key={type.type.name}/>)}
</div>
</div>
</motion.div>
</>
)
}
export default RenderPokemonInfo;
Part of my App.js that calls RenderPokemonInfo:
<div className='content-container'>
{(pokemon.length === 0) ? (
<p>Pick a pokemon!</p>
) : (
<AnimatePresence exitBeforeEnter={true}>
<RenderPokemonInfo pokemonToUse={pokemon} key={pokemon.name}/>
</AnimatePresence>
)}
</div>
When coming on screen the animations are fine, but when it leaves it just vanishes, no animation. I've seen on framer-motion's docs that you should put set the initial prop to false in AnimatePresence but when I do that it doesn't have an enter animation or an exit animation... am I doing something wrong here?

Is this entire component getting removed? If so, then you'll need to move the AnimatePresence tag up into the parent component. AnimatePresence watches for children getting removed from the DOM and runs the exit animation before removing them. If you have the AnimatePresence tag inside the component that gets removed, then it has no chance to run the exit animation, because the AnimatePresence tag is gone immediately along with the rest of the component.
You'd need something more like this (in your parent component):
<AnimatePresence>
{ pokemons.map(pokemon => <PokemonCard key={pokemon.name} />) }
</AnimatePresence>
And your PokemonCard would return the motion.div you show above (without the AnimatePresence tag)

This all came down to me not fully understanding how React uses keys. I used React.Child.toArray() to wrap everything I rendered from a map, this makes React give everything a unique key.

Related

I tried to implement this HTML Text demo with React Konva, but in vain, because

What do I want to do:
I want to make React Konva Text editable. That is, if I double click on the Text... (I can move it to anywhere in the Konva Stage) ...I can show a text area to get the edits from the user, any changes he/she would like to do to the default text.
Conditions:
Enter key (keyboard) should produce a new line.
Double click on Text should show this text area on the same offset X and Y of this Text.
Double click on div should take back to Text. Now if there was any change done to the text, then it should get reflected on the Text component's label
I tried to implement this HTML Text demo with React Konva, but in vain, because of limitations
The html Konva demo that I want to reproduce in React Konva
Things I did:
Since HTML 5.0 is incompatible if used in conjugation with React Konva components, like Text, Image, RegularPolygon ... etc.
I used { Html } from 'react-konva-utils' so that I could move that content along with the Text component like in the demo in the link above.
Things that I observed:
The HTML can take padding and margin (we can use normal html within tag.), but not top, left.
I did try to put X property of Text in the margin top of the root div and value of Text's Y property in the margin left attribute of the same div, but had to revert as it was not close to the demo above.
Code:
import React, { useState, useRef, useEffect, Fragment } from "react";
import { Html } from 'react-konva-utils';
import { Text, Transformer } from "react-konva";
/*
Konva warning: tr.setNode(shape), tr.node(shape) and tr.attachTo(shape) methods are deprecated. Please use tr.nodes(nodesArray) instead.
*/
const KText = ({ stage, id, properties, isSelected, onSelect, onChange, setActiveText }) => {
const shapeRef = useRef();
const trRef = useRef();
const [toggleEdit, setToggleEdit] = useState(false)
useEffect(() => {
if (isSelected) {
trRef.current.nodes([shapeRef.current]);
trRef.current.getLayer().batchDraw();
}
}, [isSelected]);
// console.log("KText", `properties: ${JSON.stringify(properties)}`)
// console.log("KText", ` properties.text: ${properties.text}`)
const EditTextField = () => {
var textProps
const updateText = (data) => {
textProps = data
// console.log("EditTextField", `textProps: ${JSON.stringify(textProps)}`)
}
// var mAreaPos = areaPosition()
const areaPosition = () => {
let stage1 = stage.current.getStage()
return ({
x: stage1.container().offsetLeft + properties.x,
y: stage1.container().offsetTop + properties.y,
})
};
return (
<Html >
<div style={{
margin: "200px", padding: "20px", background: "lavender",
borderRadius: 20, borderStyle: "solid", borderColor: "green",
top: areaPosition().x, left: areaPosition().y
}}
onDoubleClick={() => setToggleEdit(!toggleEdit)}>
<label htmlFor="inputText">Please enter some text below:</label><p>
<textarea onChange={(evt) => (updateText({ text: evt.target.value, id: id }))}
id="inputText" name="inputText" rows="4" cols="50" placeholder="Please enter here" />
<br />
<button type="text" onClick={() => {
setToggleEdit(!toggleEdit)
setActiveText(textProps)
}}>Close</button>
</p>
</div>{/* */}
</Html >
)
}
const MainText = () => {
return (
<>
<Fragment>
<Text
stroke={"black"}
strokeWidth={1}
onTap={onSelect}
onClick={onSelect}
onDblClick={() => setToggleEdit(!toggleEdit)}
ref={shapeRef}
// {...shapeProps}
name="text"
x={properties.x}
y={properties.y}
text={properties.text}
fontFamily={properties.fontFamily}//"Serif"
fontSize={properties.fontSize}//50
fontWeight={properties.fontWeight} //"bold"
fillLinearGradientStartPoint={{ x: 0, y: 0 }}
fillLinearGradientEndPoint={{ x: 100, y: 100 }}
fillLinearGradientColorStops={[
0,
"rgba(0,0,0,0.7)",
1,
"rgba(255,155,255,0.5)"
]}
fillPriority={"linear-gradient"}
draggable
onDragEnd={e => {
/* onChange({
...shapeProps,
x: e.target.x(),
y: e.target.y(),
});*/
}}
onTransformEnd={e => {
// transformer is changing scale
/* const node = shapeRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
node.scaleX(1);
node.scaleY(1);
onChange({
...shapeProps,
x: node.x(),
y: node.y(),
width: node.width() * scaleX,
height: node.height() * scaleY,
}); */
}}
/>
{isSelected && <Transformer ref={trRef} />}
</Fragment>
</>
)
}
const RenderThis = () => {
let inText = "" + properties.text
if (inText.trim().length === 0 || toggleEdit) {
return (
<EditTextField />
)
} else return (
<MainText />
)
}
// rendering function
return (
<RenderThis />
);
};
export default KText;

How do I set the background colour of the component with a class of card to the bgColour variable defined above?

Inside the if statement is a variable called bgColour and I want to set that to the background colour of the div with a class of card but I my attempts to access it have not worked. Can somebody tell me how to solve this? If there is a way to use a seperate css page to do this then that would be even better.
renderIssues2 = () => {
let bgColours = ['Green', 'Yellow', 'Red'];
let statusOptions = ['Available', 'Under Maintenance', 'Not Working'];
return this.state.issues.length > 0 ?
this.state.issues.map((issue, index) => {
for (let i = 0; i < statusOptions.length; i++) {
if (statusOptions[i] === issue.status) {
let bgColour = bgColours[i];
}
}
return (
<div class="card" >
<div class="card-title">{issue.issues}</div>
<div class="card-body">{issue.message}</div>
</div>
);
})
:
null;
}
the forLoop for determining the bgColour is redundant
bgColour is block scoped inside the forLoop
const { issues } = this.state;
let bgColours = ["Green", "Yellow", "Red"];
let statusOptions = ["Available", "Under Maintenance", "Not Working"];
return issues.length > 0
? issues.map((issue, index) => {
const colorPos = statusOptions.indexOf(issue.status);
const bgColor = bgColours[colorPos];
const style = bgColor ? {"backgroundColor": bgColor} : {};
return (
<div class="card" style={style}>
<div class="card-title">{issue.issues}</div>
<div class="card-body">{issue.message}</div>
</div>
);
})
: null;
};
You can also create an Object of status to colour mapping
const statusToColor = {
'Available': 'green',
'Under Maintenance': 'yellow',
'Not Working': 'red',
};
renderIssues2 = () => {
return this.state.issues.length > 0
? this.state.issues.map((issue, index) => {
const bgColor = statusToColor[issue.status];
return (
<div class="card" style={{ backgroundColor: bgColor }}>
<div class="card-title">{issue.issues}</div>
<div class="card-body">{issue.message}</div>
</div>
);
})
: null;
};
It appears as though you're assigning the variable bgColour a color value from your bgColours array but you're not using that variable when you render your card so that color is not being used anywhere.
Add the bgColour as a class to your card, then in a css file, maybe your main index.css, create classes for each of your colors.
<div className={"card " + bgColour}>
<div className="card-title">{issue.issues}</div>
<div className="card-body">{issue.message}</div>
</div>
.Green{
background-color: green;
}
.Yellow{
background-color: yellow;
}
.Red{
background-color: red;
}
Also, in React when assigning class names, you need to use className, not class.
Styling Docs

React Popper. How to move element relatively parent?

I'm trying to move Popper that is inside parent component. How i can do it?
I tried to use offset property in modifiers object.
e.g.:
offset: {
offset: 20,
}
But offset only move Popper in one direction. If placement={'top' || 'bottom'} => it moves right and left, if placement={'right' || 'left'} => it moves top and bottom.
const Layer = ({
children,
align,
inner = true,
parent,
portal,
}) => (
<Manager>
<Popper
placement={align}
modifiers={{
inner: {
enabled: inner,
},
}}
referenceElement={parent}
>
{({ ref, style, placement }) => (
<div
ref={ref}
data-placement={placement}
style={style}
>
{console.log(style)}
{children}
</div>
)}
</Popper>
</Manager>
)
Just dealt with this: Place MaterialUI Tooltip "on top" of anchor element? (React)
PopperProps={{
popperOptions: {
modifiers: {
flip: { enabled: false },
offset: {
enabled: true,
offset: '20px 20px'
}
}
}
}}
I solved it like that. Added topOffset and leftOffset props to popper child and used spread syntax. Don't think it is cleanest solution.
const Layer = ({
children,
align,
inner = true,
parent,
portal,
topOffset,
leftOffset,
}) => (
<Manager>
<Popper
placement={align}
modifiers={{
inner: {
enabled: inner,
},
}}
referenceElement={parent}
>
{({ ref, style, placement }) => (
<div
ref={ref}
data-placement={placement}
style={{
...style,
top: style.top + topOffset,
left: style.left + leftOffset,
}}
>
{console.log(style)}
{children}
</div>
)}
</Popper>
</Manager>
)

Angular2 #ViewChild ElementRef offsetHeight always 0

I'm trying to reference a component's element in my template and the height is always 0.
export class LoginComponent {
#ViewChild("loginForm", {read: ElementRef})
loginForm;
constructor() {}
ngAfterViewInit() {
console.log("form height: ", this.loginForm.nativeElement.offsetHeight);
}
click() {
console.log("form height: ", this.loginForm.nativeElement.offsetHeight);
}
}
Template
<div class="modal-content"
[style.height.px]="contentHeight">
<login-form #loginForm
(click)="click()"
[class.active]="currentForm === 'login'">
</login-form>
<register-form
[class.active]="currentForm === 'register'">
</register-form>
<div #registerSuccess class="register-success"
[class.active]="currentForm === 'registerSuccess'">
Thank you for registering
</div>
</div>
It's odd because the element is rendering fine and takes up space but even clicking after a few seconds still returns a height of 0.
https://gyazo.com/6504d4f41e6e0072df517082f63fa6ae
I just added setTimeout() in my ngAfterViewInit() function like this:
Simple way:
setTimeout(() => {
// Do what you want here
console.log(this.myElement.nativeElement.offsetHeight);
}, _timeout); // Mine worked even with _timeout = 1
And the output was not zero any-more.
Better way
And 100 percent way that works is:
let offsetHeight = 0;
const refreshInterval = setInterval(() => {
if (offsetHeight === 0) {
offsetHeight = this.giftImage.nativeElement.offsetHeight;
// Do what you want here
console.log(this.giftImage.nativeElement.offsetHeight);
} else {
clearInterval(refreshInterval);
}
}, 10);
You can set :host { display: block } for the component so it will have height. Default is display: inline. If you leave it default, width and height will be 0

onError in img tag in React

I want to replace a broken link with a default image in react. I'd typically use onerror for this but it is not working as expected. Specifically, I get repeated errors of "Cannot update during an existing state transition (such as within render)." Eventually, the default image appears, but it takes a long time (many prints of this error). This is a very similar question asked here: react.js Replace img src onerror. I tried this solution (top one, not using jQuery) but it causes the error described. I guess onError must be getting triggered continually, thus causing the constant rerendering. Any alternative solutions/fixes?
import React from 'react';
import { connect } from 'react-redux';
//import AddImageModal from '../components/AddImageModal.js';
import Button from 'react-bootstrap/lib/Button';
//import { getPostsByUserId } from 'actions'
import Posts from '../components/Posts.js';
var Modal = require('react-modal');
require('../../styles/AddImageModal.scss');
import { save_post } from '../actions';
const customStyles = {
content : {
top : '50%',
left : '50%',
right : 'auto',
bottom : 'auto',
marginRight : '-50%',
transform : 'translate(-50%, -50%)'
}
};
var MyWallScreen = React.createClass({
getInitialState: function() {
return {
modalIsOpen: false,
imageUrl: ""
};
},
openModal: function() {
this.setState({modalIsOpen: true});
},
afterOpenModal: function() {
// references are now sync'd and can be accessed.
this.refs.subtitle.style.color = '#f00';
},
closeModal: function() {
this.setState({modalIsOpen: false});
},
setUrl: function(e,val)
{
if (e.keyCode === 13)
{
this.setState({
imageUrl: val
});
}
},
resetImageUrl: function()
{
this.setState({
imageUrl: ""
});
},
onError: function() {
this.setState({
imageUrl: "default.jpg"
});
},
render: function() {
const { userPosts, dispatch } = this.props;
return (
<div>
<button onClick={this.openModal}>Add Image</button>
{/* The meat of the modal. */}
<Modal
isOpen={this.state.modalIsOpen}
onAfterOpen={this.afterOpenModal}
onRequestClose={this.closeModal}
style={customStyles} >
<div className="modalBox">
<h2 className="modalBanner">Add an image link</h2>
<input ref="urlInput"
className="modalInput"
onKeyDown={e=>this.setUrl(e,this.refs.urlInput.value)}/>
{this.state.imageUrl ?
<img className="modalImage"
src={this.state.imageUrl}
onError={this.onError()}/>
:<div className="modalImage"></div>
}
<div>
<Button className="modalButton" bsStyle = "success"
onClick = {() => {
dispatch(save_post(0,this.state.imageUrl));
this.closeModal();
this.resetImageUrl();
}}>
Post
</Button>
<Button className="modalButton" bsStyle = "danger"
onClick = {() => {
this.closeModal();
this.resetImageUrl();
}}>
Cancel
</Button>
</div>
</div>
</Modal>
<Posts posts={userPosts}/>
</div>
);
}
});
function mapStateToProps(state, ownProps) {
return {
userPosts: state.posts[0]
}
}
MyWallScreen = connect(mapStateToProps)(MyWallScreen);
export default MyWallScreen;
The code is calling this.onError rather than passing a reference to it. Every call to render is calling this.onError(). Remove the parentheses, and see if that fixes it:
<img className="modalImage"
src={this.state.imageUrl}
onError={this.onError()}/> // `onError` is being called here
Fixed version:
<img className="modalImage"
src={this.state.imageUrl}
onError={this.onError}/> // `onError` is being passed as a reference here
You can replace the image broken link without keeping image urls in state.
<img
onError={(event)=>event.target.setAttribute("src","default-image-link")}
src="image-broken-link"
/>
I tried this way and it works for me hope this will work for you.
Make sure to put the below useState within same function where u used img tag.
const [error, setError] = useState(false);
<img src={`${error ? DefaultLogo
:`${AgentApiURL}/publicservices/images/${props.item[0]}`}`}
alt="plating"
onError={() => setError(true) }
/>