I am wondering if it is possible to debounce a jsx element's rendering. I have an animation of a panel expanding that has some content in it. If the panel is empty (just the background) the animation is smooth and works as expected. The animation and associated components are all from Material-UI. The issue arises when the content is in the panel already so the animation (width expansion) just skips out to the width of the content making the animation look choppy. Here is a similar example to what I am referring to. This code is my code I am using in my panel and they work the same in terms of the expansion. Only difference is the content in this example is just lorem ipsum so the animation appears to work fine. Is it possible to debounce the <CardContent /> component's rendering like this?
{ open &&
(
_.debounce(e => {
return (
<CardContent>
{/*content in here*/}
</CardContent>
)
}, 300)
)
}
or something similar (this doesn't work) so the panel will be fully expanded through the animation before the content is rendered?
I'd like to add a comment however my reputation isn't above 50 yet so unfortunately I can't :p.
JS Animation
To answer your question, if you are using JavaScript animations like GSAP or AnimeJS you can have a state flag called. isAnimating = null. Then when your animation starts set the flag to true and when it's finished set it to false. Then in your render() method write a conditional render like this.state.isAnimating === false && <CardContent />.
CSS Animation
If you are using CSS animation you can use the transitionend event to wait for the animation to end. Therefore you have to create a ref on the animating DOM element to which you can attach the event.
Does that help you a bit? Please let me know if you have any other questions.
Here is a solution using react spring library
I added a state variable in order to display or not the card content
const [contentVisible, setContentVisible] = React.useState(false);
I created a spring to handle the width transition, I set contentVisible to true as soon as the width approaches the 300
const { width } = useSpring({
width: open ? 300 : 120,
onFrame: ({ width }) => setContentVisible(width > 299.8)
});
Finally, I created an animated card component
const AnimatedCard = animated(Card);
...
<AnimatedCard classes={{ root: classes.card }} style={{ width }}>
Related
I am trying to find a solution to modify the height of the div for react based on how many elements it will consist to grow automatically.
So, to do the same i have chosen jquery
ul.each(function() {
const self = $(this);
const { listCount, ulCount } = getListItemsCount(self);
const ruleHeight = listCount * listDefaultHeight;
const children = $(self).children();
/* Swapping of elements to adjust the heights */
if (children.length - 1 === 2 && ulCount === 1) {
if ($(children[2]).data('count') > $(children[1]).data('count')) {
$($(self).children()[2]).insertBefore($($(self).children()[1]));
//ruleHeight += 25;
}
}
$(self)
.find('div')
.css({ height: `${ruleHeight}px` });
});
The above code is happening inside componentDidMount(). The reason I am doing here, we are not sure how much height we need to increase as the div position is absolute and also depending upon the first level content, we are swapping the divs as well as a high-level overview.
The blue and ping is an absolute div whose height is growing accordingly, if the first level data is less and second level has nested and more list items, the height was not adjusting, so swapping the divs and its working.
The question here is: Is it the right approach to handle this with jquery?
React DOM is not updated now? How to update the react dom as well, if suppose render method is called, will the jquery code is written become obsolete?
Is there a possibility to adjust the height using flex automatically without using jquery for absolute positions or by just css will be awesome fix?
Please guide the best practice to do the same
I would suggest do not use jquery instead , write a function to calculate height of the div in componentDidMount and set the height of the div in render method.
Here is an example
componentWillMount() {
this.setDivHeight();
}
return () {
render(
<div style={{height: this.state.divHeight}}>
</div>
)
}
This just for reference, you have to call a function to set change value of state divHeight to re render the component and the div height will also change on each render. Also would suggest user in JSX to code your components. Tips about JSX
I am using Expo and the latest version of React-Native and I want to provide a subtle tactile feedback for Components like a View or Button.
I currently use animatable to start a pulse animation on the onPress() event but the animation only fires once the finger is released.
I want a subtle size reduction whilst press then a smooth tween back when released - that would feel elegant and not annoying to me.
Can this be done? I thought Animation or Animatable would have easily supported this but I can’t find any similar examples.
You could make your own touchable using the Gesture Responder System
Basically you'll use the props onStartShouldSetResponder, onResponderGrant, and onResponderRelease passed to an Animated.View.
class MyTouchable extends React.PureComponent {
render(){
<Animated.View
style={{ your styles, and animation values }}
onStartShouldSetResponder={() => true}
onResponderGrant={({ nativeEvent }) => {
//run your animations here
}}
onResponderRelease={({ nativeEvent }) => {
//animate back to zero, call your onPress function
//passed via props
}}
/>
}
}
I am developing an app with react native. I have this UI element which is similar to that of Maps in iOS, in which you slide a panel from the bottom and inside it, there is a scrollable list.
For the slide-out panel, I am using a component called rn-sliding-up-panel. It has several props as event listeners. For example
<SlidingUpPanel
allowDragging={/*Boolean*/}
onDragStart={()=>{} /*When it is about to be dragged*/}
onDrag={()=>{} /*When it is being dragged*/}
onDragEnd={()={} /*When the user is no longer touching the screen*/}
></SlidingUpPanel>
Inside it, I have a <ScrollView> containing a <List> from react-native-elements. As far as I know, it has only one vent listener, being:
<ScrollView onScroll={()=>{}}></ScrollView>
My issue is that scrolling on the list actually causes the panel to close (it closes by sliding down). I found a work-around by adding a state, and modfiying it onScroll:
state = {
dragPanel: true,
}
/*===========================================*/
<SlidingUpPanel allowDragging={this.state.dragPanel}>
<ScrollView onScroll={()={ this.setState({dragPanel: false}) }}></ScrollView>
</SlidingUpPanel>
However, I cannot find a way to restore the dragging, and it doesn't fire up as efficiently.
TL;DR
Is there an eficient way to implement a ScrollView inside a SlidingUpPanel without the events of each overlapping? Maybe using something similar to function(e){e.preventDefault();}?
To properly disable / restore outer scroll dragging, do
_onGrant() {
this.setState({ dragPanel: false });
return true;
}
_onRelease() {
this.setState({ dragPanel: true });
}
constructor(props) {
super(props);
this._onGrant = this._onGrant.bind(this);
this._onRelease = this._onRelease.bind(this);
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder: this._onGrant,
onPanResponderRelease: this._onRelease,
onPanResponderTerminate: this._onRelease,
});
}
render() {
<SlidingUpPanel allowDragging={this.state.dragPanel}>
<ScrollView
{...this._panResponder.panHandlers}
/>
</SlidingUpPanel>
}
From what I had been searching for a long time, preventDefault() is a pure web-javascript thing, I think there are no preventDefault in react-native.
From document section Handling Touches, react-native just use javascript to simulate Objc (iOS) & Java (Android) events.
Set the minimumDistanceThreshold property to something around 50. Maybe 30 for small screens and 50-60 for bigger ones. Do it like so:
<SlidingUpPanel minimumDistanceThreshold={isSmallScreen ? 30 : 50}>
<ScrollView style={{flex: 1}}>
</ScrollView>
</SlidingUpPanel>
It might be late for an answer but use your scroll view as absolute positioned and position it accordingly.
Think of the scroll view as a pop-up dialog that appears in front of the backdrop behind it. Upon clicking the backdrop, the pop-up dismisses. Apply similar logic to the issue by letting scroll view in front of the slide up panel.
I'm trying to setup a Marquee in React if a piece of text is greater than its container but I can't get the correct width of the container, even after the component has rendered.
I read in another answer React “after render” code? that you have to use requestAnimationFrame which I'm trying and it's still not working.
If I log the width of the container it shows a width of 147px which is set using min-width in the stylesheet but the correct width should be 320px which is set using a media query when the screens min-width is 600px.
This is a child component, the parent is rendered inside an iFrame if it makes any difference and the iFrame's width is well over 600px.
The JS:
module.exports = React.createClass({
componentDidUpdate: function () {
// Setup marquee
this.initMarquee();
},
render: function () {
// Setup marquee
this.initMarquee();
var artistName = this.props.artist.artistName;
var trackName = this.props.track.trackName;
return (
<div className="MV-player-trackData no-select" ref="mvpTrackData">
<div className="MV-player-trackData-marquee-wrap" ref="mvpMarqueeWrap">
<div className="MV-player-trackData-marquee" ref="mvpMarquee">
<a className="MV-player-trackData-link no-select" href={this.props.storeUrl} target="_blank">
<span id="mvArtistName">{artistName}</span> – <span id="mvTrackName">{trackName}</span>
</a>
</div>
</div>
</div>
)
},
initMarquee: function () {
if ( typeof requestAnimationFrame !== 'undefined' ) {
//store a this ref, and
var self = this;
//wait for a paint to setup marquee
window.requestAnimationFrame(function() {
self.marquee();
});
}
else {
// Suport older browsers
window.setTimeout(this.marquee, 2000);
}
},
marquee: function () {
var marquee = React.findDOMNode(this.refs.mvpMarquee);
var marqueeWrap = React.findDOMNode(this.refs.mvpMarqueeWrap);
// If the marquee is greater than its container then animate it
if ( marquee.clientWidth > marqueeWrap.clientWidth ) {
marquee.className += ' is-animated';
}
else {
marquee.className = marquee.className.replace('is-animated', '');
}
}
});
The CSS:
.MV-player-trackData-marquee-wrap {
height: 20px;
line-height: 20px;
min-width: 147px;
overflow: hidden;
position: relative;
width: 100%;
#media only screen and (min-width : 600px) {
min-width: 320px;
}
}
I've tried a number of different solutions including laidout and react-component-width-mixin but neither of them work. I tried react component width mixin because in another part of my app I'm trying to get the value of window.innerWidth but that also returns 0 after rendering, unless I set a timeout for around 2 seconds, unfortunately though sometimes 2 seconds isn't long enough due to data loading and other callbacks so this can brake easily.
Any help is really appreciated. Thanks.
Update:
One of the answers correctly pointed out i should be calling this.initMarquee(); inside componentDidMount which I was doing, unfortunately I pasted the wrong code from when I was testing to see if it made a difference calling it inside render. The correct code looks like this:
componentDidMount: function () {
// Setup marquee
this.initMarquee();
},
Unfortunately this doesn't work either, I still receive the incorrect width for marqueeWrap.
Update: 24/06/2015
Just to clarify, this is the marquee effect I'm trying to achieve, only when the text is bigger than its container as it's pointless scrolling it when it is not bigger.
Also here is a link to a Github Issue from the React team speaking about why React renders before the browser paints. - So as that is the case, I want to know how do I reliably get the width of the element in question.
One possible problem that can occur is that your CSS has not loaded yet when componentDidMount fires. This can happen with webpack and including the css in your bundle that also contains your js even if you have included the css before your component in the project.
There are several issues, as you've pointed out, in dealing with the virtual DOM. You definitely don't want to be attempting to use jQuery to manipulate DOM nodes and React is likely to scream at your for attempting to.
There's a library called React-Context which would do exactly what you're asking. You would expose its api to your wrapper component and then be able to listen to events on components within the virtual dom.
This library is a little dusty, however it should work with the code sample you shared.
You should not call this.initMarquee(); in the render() method.
In general, you should not work with the DOM at all in the render() method.
Try to call this.initMarquee(); in the componentDidMount method.
(and I really don't understand the usage of requestAnimationFrame in this case)
I've created a simple component on reactjs (kind of a value-picker: plunker)
Now, I want the upper and lower parts of the control to be hidden (opacity=0) and animate to opacity=1 when user hovers the central div (.range-picker-selection) with mouse.
Since there's no way I can operate with DOM directly from functions of my component (reactjs discourages this) I guess I should change the state ( to something like state.expanded = true) and rely on reactjs to reflect the change.
But how can I achieve the animation effect?
With jQuery I would have used element.animate({opacity:0}) but now I don't have access to DOM
Something like this?
http://plnkr.co/edit/5tLWNTtpsSWBUCgnKwQO?p=preview
I updated the state to have a hovered attribute and two mouse enter / leave methods to set the value which are bound the .range-picker-selection element in the render function
<div className="range-picker-selection" onMouseEnter={this.onMouseEnterHandler} onMouseLeave={this.onMouseLeaveHandler}>
onMouseEnterHandler: function () {
this.setState({
hovered: true
})
},
onMouseLeaveHandler: function () {
this.setState({
hovered: false
})
},
I also updated the render function to set the opacity to 0 or 1 accordingly for the elements.
style={{opacity: this.state.hovered ? 1 : 0}}
Finally, the actual animation is done with CSS animations
transition: opacity 1s;
I hope this helps