React-motion rendering on every animation - javascript

I'm getting really bad performance using React-Motion (https://github.com/chenglou/react-motion). I'm animating the height of a dropdown from a table row from 0 to 260.
constructor() {
this.state = {
opened: false
}
this.handleRowClick = this.handleRowClick.bind(this)
}
handleRowClick() {
this.setState({
opened: !this.state.opened
})
}
render() {
<Motion style={{height: spring(this.state.opened ? 260 : 0, {stiffness: 140, damping: 30})}}>
{(height) =>
<div onClick={this.handleRowClick}>
<div style={height}>
...stuff goes here
</div>
</div>
}
</Motion>
}
The animation is working as expected, but upon logging the height every time it renders all of this in the span of ~5 seconds (which is WAY too long):
Maybe I misread something in the docs, but is there a way to avoid lag on the animation?

You'll need to apply the transition styles to a div and render a component inside it which implements shouldComponentUpdate (eg. with React.PureComponent) to prevent it from rerendering when not needed.
render () {
<Motion
style={{
height: spring(
this.state.opened ? 260 : 0,
{ stiffness: 140, damping: 30 }
)
}}
>
{(height) =>
<div style={height}>
<YourComponent />
</div>
}
</Motion>
}
And MyComponent might be something like class MyComponent extends React.PureComponent or using a HOC like pure from recompose. This way MyComponent will only update when it's props changes.

Related

Add hover effect to react div using inline styling

I have a div which takes the shape of a circle the css property to display a circle is taken from the circle class. The color of the circular div is taken from the inline styling. Here a function called Status() is used where it will return a hex color code. The circle renders with the colors according to the status we pass to the Status function. To achieve the hover effect i added a ':hover' property to the styling object but it doesn't work. Here is the code that i have tried. Any idea on how to achieve this?. i need to add a boarder/glow to the circle on mouse hover.
<div
className="circle"
style={{
backgroundColor: Status('new'),
':hover': {
boxShadow: `0px 0px 4px 2px ${Status('complience')}`,
},
}}
/>
Try adding & before :hover
This is not possible with inline styles, you may want to use onMouseEnter and onMouseLeave props to get the hover state and use it, for example :
class MyComponent extends React.Component {
state= {
hover: false,
}
handleMouseEnter = () => {
this.setState({ hover: true });
}
handleMouseLeave = () => {
this.setState({ hover: false });
}
render() {
const { hover } = this.state;
return(
<div
className="circle"
style={{
backgroundColor: Status('new'),
...(hover && { boxShadow: `0px 0px 4px 2px ${Status('complience')}`}),
}}
onMouseEnter={this.handleMouseEnter} // Or onMouseOver
onMouseLeave={this.handleMouseLeave}
/>
)
}
}
Alternatives :
Use a third party styling library (e.g. Styled-components)
Use classnames / css stylesheets

How to animate image for 5 seconds in React using CSS?

I'm using React with Redux, and I have the following situation. In my component I have a div that holds and image, and the component is also receiving a property from my Redux state which is called showIcon. So, if showIcon is not null, I want the image to be displayed for 5 seconds, and once the 5 seconds passes, I want it to disappear and set the showIcon value to null by dispatching an action that I have like updateShowIcon(null);. How can I do this properly in React, and how can I use CSS to show and animate the icon as I want?
import React, { Component } from 'react';
class MyComp extends Component {
render() {
return (
<div style={styles.mainDiv}>
<div style={styles.childDiv}>
{
this.props.showIcon &&
<div style={styles.iconStlyes}>
<img src={process.env.PUBLIC_URL + '/icons/myicon.png'}/>
</div>
}
// partially removed for brevity, some other component
</div>
</div>
);
}
}
const styles = {
iconStlyes: {
position: 'absolute',
zIndex: 10,
},
mainDiv: {
overflow: 'auto',
margin: 'auto',
height: 'calc(100vh - 64px)',
padding: 0,
},
childDiv: {
height: 'calc(100vh - 64px)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
},
};
export default MyComp;
Whenever I detect a change in componentWillReceiveProps I would create a timer and dispatch the action. Remember to clear the timeout on componentWillUnmount.
The idea is based on you showing and hiding the icon with css and not with react conditional rendering, so once you need to show the icon you add the class show or remove it once you don't need to show it.
It would probably look like this:
componentWillReceiveProps(nextProps){
if (nextProps.showIcon && nextProps.showIcon !== this.props.showIcon){
this.timer = setTimeout(() => {nextProps.updateShowIcon(null)}, 5000);
}
}
componentWillUnmount(){
clearTimeout(this.timer);
}
render() {
const {showIcon} = this.props;
return (
<div style={styles.mainDiv}>
<div style={styles.childDiv}>
<div style={styles.iconStlyes} className={`${showIcon ? 'show':''} icon-container`}>
<img src={process.env.PUBLIC_URL + '/icons/myicon.png'}/>
</div>
</div>
</div>
);
}
and your css for a simple fade animation:
.icon-container{
opacity: 0;
transition: opacity: 500ms ease-in;
}
.icon-container.show{
opacity: 1;
}
If it is important for you to use the store state then you can manage the showIcon property via componentWillReceiveProps(nextProps) and do something like:
componentWillReceiveProps(nextProps){
if(!this.props.showIcon && nextProps.showIcon){
setTimeout(()=>dispatch(updateShowIcon(null)),5*1000);
}
//manage clear timeout if necessary
}
But for the animation part its better to use the showIcon property as a class and not for adding/removing it from the DOM, like:
<div style={styles.iconStlyes} className={this.props.showIcon?'show':'hide'}>
<img src={process.env.PUBLIC_URL + '/icons/myicon.png'}/>
</div>
and the styles should manage it:
iconStyles: {
position: 'absolute',
zIndex: 10;
transition: //effects of specified or all attributes
&.show{
visibility: visible;//display:block
}
&.hide{
visibility: hidden;//display:none
}
}

Dynamic components: Calling element by ref

One part of my application is an image gallery. When the user clicks on an image, I want to put an opaque layer over the image to visualize that it is selected.
When I display the layer, and I click on the image to deselect it, naturally I'm actually clicking on the layer.
Here's the relevant ReactJS code to show what I mean:
{images.map((i, idx) => (
<div key={"cont"+idx} className="container">
<img src={i.images} ref={"img"+idx} />
<div onClick={this.handleIconDeselect} id={"div_"+idx}></div>
</div>
)
)}
I tried to give the img a unique ref (as shown above), but I'm having trouble selecting the correct img.
This is how I try to select the correct image:
handleIconDeselect = (event) => {
var imgref = "icon"+event.target.id.split("_").pop();
this.refs.imgref.click();
}
However, I get the following error message:
TypeError: Cannot read property 'click' of undefined
How can I select the correct image while using unique refs?
Alternatively, if the way I'm trying to achieve this is bad practice (I know you should only use refs when absolutely necessary), what is a better way to do it?
Try use state as here: https://codesandbox.io/s/m4276x643y
Maybe that is not the best way but it give you an rough idea.
import React, { Component } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
const coverStyle = {
position: "fixed",
top: 0,
left: 0,
zIndex: -1,
opacity: 0,
width: "100%",
height: "100%",
background: "#000"
};
const coverStyleShow = {
...coverStyle,
zIndex: 1,
opacity: 1
};
const imgShow = {
zIndex: 10,
position: "relative"
};
const images = [
"https://dummyimage.com/100.png/f10/fff",
"https://dummyimage.com/100.png/f20/fff",
"https://dummyimage.com/100.png/f30/fff",
"https://dummyimage.com/100.png/f40/fff",
"https://dummyimage.com/100.png/f50/fff",
"https://dummyimage.com/100.png/f60/fff",
"https://dummyimage.com/100.png/f70/fff"
];
class App extends Component {
constructor(props) {
super(props);
this.state = {
cover: coverStyle,
img: imgShow,
imgId: null,
imgShow: false
};
}
handleImageClick = (target, idx) => {
// you can do something with this "target"...
this.setState({
cover: coverStyle,
coverShow: coverStyleShow,
imgId: idx,
imgShow: !this.state.imgShow
});
};
render() {
return (
<div>
<Hello name="CodeSandbox" />
<h2>Start editing to see some magic happen {"\u2728"}</h2>
<div>
{images.map((img, idx) => (
<img
key={img}
src={img}
style={idx === this.state.imgId ? this.state.img : null}
onClick={event => this.handleImageClick(event.target, idx)}
alt="dummy img"
/>
))}
</div>
<span
style={this.state.imgShow ? this.state.coverShow : this.state.cover}
/>
</div>
);
}
}
render(<App />, document.getElementById("root"));

Using .map() to generate elements with Refs

Is there a best way to go about using .map() to output elements with refs?
I'm doing this so I can track the elements (generated from state) for collision detection in an endless runner style game (without canvas).
I’ve tried 3 different approaches and matter what I do I seem to get ref=ref() when it should be ref=“tango” (as per the state). Image here - https://i.imgur.com/oeStcHb.png
EDIT: for others in future, I have since learned that ref=ref() is expected behaviour to indicate a callback is associated with it, instead of the old way of showing the actual ref.
Code being used-
this.state = {
people: {
tango: { height: 10, weight: 200 },
cash: { height: 20, weight: 300 }
}
};
...
outputStatePeople(key) {
return(<span className="db" key={key} ref={key}>name: {key}</span>)
}
...
render() { return (
<div>
{Object.keys(this.state.people).map(key => this.outputStatePeople(key))}
</div>
)}
The above use a function to generate dom, but this also happens if outputting inline html and when using a component.
Any help would be greatly appreciated! 😀
Define a function for a ref and store the refs in the class variable object like
constructor(props) {
super(props);
this.state = {
people: {
tango: { height: 10, weight: 200 },
cash: { height: 20, weight: 300 }
}
};
this.domRefs = {};
}
outputStatePeople(key) {
return (
<span className="db" key={key} ref={(ref) => {this.domRefs[key] = ref}}>
name: {key}, height: {this.state.people[key].height}
</span>
);
}
You can read more about Refs and Dom Here

How to get DOM element within React component?

I'm rendering multiple of the same component, each with their own tooltip. Can I write code that will only look within the HTML of each component, so I'm not affecting all the other tooltips with the same class name? I'm using stateless components. Here is the code:
OptionsComponent.js:
import React from 'react';
const OptionsComponent = () => {
const toggleTooltip = event => {
document.getElementsByClassName('listings-table-options-tooltip').classList.toggle('tooltip-hide');
event.stopPropagation();
};
return (
<div className="inline-block">
<span onClick={toggleTooltip} className="icon icon-options listings-table-options-icon"> </span>
<div className="tooltip listings-table-options-tooltip">
Tooltip content
</div>
</div>
);
};
Backbone.js has something like this, allowing you to scope your document query to begin within the view element (analogous to a React component).
With React, you don't want to modify the DOM. You just re-render your component with new state whenever something happens. In your case, since you want the OptionsComponent to track its own tooltip state, it really isn't even stateless. It is stateful, so make it a component.
It would look something like this:
class OptionsComponent extends React.Component {
state = {
hide: false
};
toggleTooltip = (ev) => this.setState({ hide: !this.state.hide });
render() {
const ttShowHide = this.state.hide ? "tooltip-hide" : "";
const ttClass = `tooltip listings-table-options-tooltip ${ttShowHide}`;
return (
<div className="inline-block">
<span onClick={this.toggleTooltip} className="icon icon-options listings-table-options-icon"> </span>
<div className={ttClass}>
Tooltip content
</div>
</div>
);
// Alternatively, instead of toggling the tooltip show/hide, just don't render it!
return (
<div className="inline-block">
<span onClick={this.toggleTooltip} className="icon icon-options listings-table-options-icon"> </span>
{/* do not render the tooltip if hide is true */}
{!this.state.hide &&
<div className="tooltip listings-table-options-tooltip">
Tooltip content
</div>
}
</div>
);
}
}
You should use refs.
Slightly modified from React docs:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.focus = this.focus.bind(this);
}
focus() {
var underlyingDOMNode = this.textInput; // This is your DOM element
underlyingDOMNode.focus();
}
render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in this.textInput.
return (
<div>
<input
type="text"
ref={(input) => this.textInput = input} />
<input
type="button"
value="Focus the text input"
onClick={this.focus}
/>
</div>
);
}
}
A comfortable approach would be modifying your toggleTooltip method this way:
...
const toggleTooltip = event => {
event.target.parentNode.querySelector('.tooltip').classList.toggle('tooltip-hide');
};
...
I would however recommend having a state to represent the tooltip displaying or not.
With https://github.com/fckt/react-layer-stack you can do alike:
import React, { Component } from 'react';
import { Layer, LayerContext } from 'react-layer-stack';
import FixedLayer from './demo/components/FixedLayer';
class Demo extends Component {
render() {
return (
<div>
<Layer id="lightbox2">{ (_, content) =>
<FixedLayer style={ { marginRight: '15px', marginBottom: '15px' } }>
{ content }
</FixedLayer>
}</Layer>
<LayerContext id="lightbox2">{({ showMe, hideMe }) => (
<button onMouseLeave={ hideMe } onMouseMove={ ({ pageX, pageY }) => {
showMe(
<div style={{
left: pageX, top: pageY + 20, position: "absolute",
padding: '10px',
background: 'rgba(0,0,0,0.7)', color: '#fff', borderRadius: '5px',
boxShadow: '0px 0px 50px 0px rgba(0,0,0,0.60)'}}>
“There has to be message triage. If you say three things, you don’t say anything.”
</div>)
}}>Yet another button. Move your pointer to it.</button> )}
</LayerContext>
</div>
)
}
}

Categories

Resources