I have a ajax transition which is applyed to a DOM element by setting the className "fetching" in the render()-Method whenever the Component fetches data from the server. After fetch() returns, I let that className be removed so the Component can transition back to its original appearance.
It works like this:
fetchStuff() {
this.setState({fetching: true})
fetch(stuff)
.then(answer => {
// do stuff with the answer
this.setState({fetching: false})
)
}
// and then ...
render() {
let {fetching} = this.state
return <div className={'my-component' + (fetching ? ' fetching' : '')}></div>
}
.my-component {
transition: all .3s;
opacity: 1;
}
.my-component.fetching {
opacity: 0;
}
The start transition works fine! But when React removes the "fetching" className, the Component jumps to its original appearance without transition.
What can I do?
Thanks for your help :)
UPDATE:
Somehow this problem occurs more often in Chrome than in Firefox.
UPDATE 2:
Preventing unnecessary rerenders by using shouldComponentUpdate() on the parent Component made it better but still the transition sometimes skips or is way too fast. But sometimes it is now smooth as it should be.
Related
I have a React control that renders a bunch of images. My goal is to avoid the flickering that is caused by an unknown time it takes React to load the images (yes, I know about inline image loading, let's pretend it doesn't exist for a moment)
I have an initialized array in my class:
this.loadedImages = [];
For this purpose I use onLoad in this manner:
render () {
let items = this.props.images.map((value, index) => {
let style = {};
if (this.isImageLoaded(index))
style = value.style;
else
style = {visibility: 'hidden'};
return <img
key={ index }
onClick={ this.onClick }
onLoad={ this.onLoad(index) }
style={ style }
src={ value.image }
alt={ value.alt}/>
});
return (
<div>
{items}
</div>
);
}
}
my onLoad and isImageLoaded look like this:
onLoad = (index) => {
if (!this.isImageLoaded(index)) {
this.loadedImages.push(index);
}
};
isImageLoaded = (index) => {
let isloaded = this.loadedImages.includes(index);
if (isloaded)
console.log(index + " is loaded!");
else
console.log(index + " is NOT loaded ");
return isloaded;
};
The issue is that once my page loads, the images switch from a "not loaded" into a "loaded" mode -- BUT there is only ONE RENDER that occurs before the images are loaded, thus the {visibility: 'hidden'} style remains permanent.
So my page loads without images. Now, if I click my component even once, the images will appear correctly because the component is forced to re-render (since now the images are loaded). BUT there is no option for me to force such a re-draw programmatically from the onLoad function as I'm getting a warning I should not be doing that from render...
My question is: how can I break the chicken/egg problems here and re-render my component once any image completes its loading.
I suggest combining your loadedImages data with the your other image state (as a boolean flag on each) and updating it using setState every time one loads (your headaches are due to this separation and the fact that you are having to manually keep them synchronised).
Then map over the single array of images (including loading state), using something like the src for the key.
I'm trying to make a small sprite-based game with ReactJS. The green dragon (was taken from HMMII) is flying across the hexagonal field and it's behavior depends on mouse clicking. The sprites change each other with speed depending on a specially chosen time constant - 170ms. More precisely: there is a div representing the dragon and it's properties (top, left, width, height and background-image) always are changing.
At the first stage of the development I've faced with irritating blinking and flickering by rerendering the image. How can avoid it?
Below are described multiple ways I've used with some previews made with Surge. The strongest effect is watched in Google Chrome but in Firefox also are troubles.
0) At first I've tried to use CSS-animation based on #keyframes, but it was no good due to fade effect. And I don't need any fade effects at all, I need rapid rerendering.
1) This is the most straightforward attempt. After clicking on a particular field, componentWillReceiveProps is creating the list of steps and then all of this steps are performing consistently. Also I've tried to use requestAnimationFrame instead of setTimeout but with the same troubles.
makeStep() {
const {steps} = this.state;
this.setState((prevState, props) => ({
steps: steps.slice(1),
style: {...}
}));
}
render() {
const {steps, style} = this.state;
steps.length ? setTimeout(this.makeStep, DRAGON_RENDER_TIME):
this.props.endTurn();
return (<div id="dragon" style={style}></div>);
}
Here is the result: http://streuner.surge.sh/ As you can see, dragon is often disapearing by launching and landing, it fly with skipping some sprites.
2) I've tried to test method describen in article:
https://itnext.io/stable-image-component-with-placeholder-in-react-7c837b1ebee
In this case I've changed my div with background-image to other div containing explicit img. At first, this.state.isLoaded is false and new sprite will not appear. It appears only after the image has been loaded with onLoad method. Also I've tried to use refs with attempt watch for complete-property of the image but it's always true - maybe because size of the image is very small.
setLoaded(){
this.setState((prevState, props) => ({
isLoaded: true
}));
}
render() {
const {isLoaded, steps, style} = this.state;
if(isLoaded) {
steps.length ? setTimeout(this.makeStep, DRAGON_RENDER_TIME):
this.props.endTurn();
}
return (<div id="wrap" style={{top:style.top, left:style.left}} >
<img id="dragon" alt="" src={style.src} onLoad={this.setLoaded}
style={{width:style.width,
height: style.height,
visibility: isLoaded ? "visible": "hidden"}}/>
</div>);
}
Here is the result: http://streuner2.surge.sh/ There's no more sprite skipping but the flickering effect is much stronger than in first case.
3) Maybe it was my best attempt. I've read this advice: https://github.com/facebook/react-native/issues/981 and decided to render immediately all of the step images but only the one with opacity = 1, the others have opacity = 0.
makeStep(index) {
const {steps} = this.state;
this.setState((prevState, props) => ({
index: index + 1,
steps: steps.map( (s, i) => ({...s, opacity: (i !== index) ? 0: 1}))
}));
}
render() {
const {index, steps} = this.state;
(index < steps.length) ?
setTimeout(() => this.makeStep(index), DRAGON_RENDER_TIME):
this.props.endTurn();
return ([steps.map((s, i) =>
<div className="dragon" key={i} style={s}></div>)]);
}
It's possible to see the result here: http://streuner3.surge.sh/ There's only one flickering by starting new fly with rerendering all sprites. But the code seems to me more artificial.
I would like to emphasize that the behavior always depends on browser, in Firefox it's much better. Also there are differences with variety of flys in the same browser: sometimes there's no flickering effect but in most of cases it unfortunately is. Maybe I don't understand any basic notion of rerendering images in browser.
I think you should shift your attention from animation itself and pay more attention to rerendering in React, each time when you change Image component state or props it is rerendering. Read about lifecycle methods and rerendering in React docs.
You change state very fast(in your case it's almost 6 times per second), so I suppose that some of the browsers are not fast enough with Image component rerendering. Try to move out of Image state variables which updates so fast and everything will be ok
I know the answer is late, but posting my answer here in case someone still wants to find a solution and because I've found this drives some traffic.
A simple workaround is to add a CSS transition property to the image like the below:
transition: all .5s;
it does not prevent the image re-rendering, but at least it does prevent the image flickering.
I'm trying to make scroll to the top, if a certain condition is met, in the component's componentWillReceiveProps event ... but nothing happens:
componentWillReceiveProps(nextProps) {
// some code...
if (newQuery === query && this.scrollViewRef.current) {
console.log('Should scroll to top'); // << logs successfully
this.scrollViewRef.current.scrollTo({
x: 0,
y: 0,
duration: 500,
animated: true,
});
}
}
Code snippet of how I created ref for the scrollView:
class SearchResult extends React.Component {
constructor(props) {
super(props);
this.scrollViewRef = React.createRef();
}
//...
}
render method:
render () {
return (
<ScrollView
ref={this.scrollViewRef}
contentContainerStyle={{ marginVertical: 8 }}
>
...
)
}
I also tried to scroll manually via a button press ... doesn't work as well
Any help ?
I figured this out ...
The scrollView worked perfectly in an isolated env ( a brand new project ) ...
I thought the issue could be in the container of that scrollview ... and I found that the parent component has also a ScrollView ... once i removed it, everything worked perfectly.
For those people who use useRef() method and gets 'xRef.scrollTo' is not a function error, try to use it like xRef.current.scrollTo({[YOUR_PARAMS]}).
I didn't know this current thing and was getting crazy.
React Native docs say:
Note: The weird function signature is due to the fact that, for historical reasons, the function also accepts separate arguments as an alternative to the options object. This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.
Maybe try scrollToOffset method, if you are also using FlatList with ScrollView?
I have an alert box to confirm that the user has successfully subscribed:
<div className="alert alert-success">
<strong>Success!</strong> Thank you for subscribing!
</div>
When a user sends an email, I'm changing the "subscribed" state to true.
What I want is to:
Show the alert box when the subscribed state is true
Wait for 2 seconds
Make it fade out
How can I do this?
May 2021 update: as tolga and Alexey Nikonov correctly noted in their answers, it’s possible to give away control over how long the alert is being shown (in the original question, 2 seconds) to the transition-delay property and a smart component state management based on the transitionend DOM event. Also, hooks are these days recommended to handle component’s internal state, not setState. So I updated my answer a bit:
function App(props) {
const [isShowingAlert, setShowingAlert] = React.useState(false);
return (
<div>
<div
className={`alert alert-success ${isShowingAlert ? 'alert-shown' : 'alert-hidden'}`}
onTransitionEnd={() => setShowingAlert(false)}
>
<strong>Success!</strong> Thank you for subscribing!
</div>
<button onClick={() => setShowingAlert(true)}>
Show alert
</button>
(and other children)
</div>
);
}
The delay is then specified in the alert-hidden class in CSS:
.alert-hidden {
opacity: 0;
transition: all 250ms linear 2s; // <- the last value defines transition-delay
}
The actual change of isShowingAlert is, in fact, near-instant: from false to true, then immediately from true to false. But because the transition to opacity: 0 is delayed by 2 seconds, the user sees the message for this duration.
Feel free to play around with Codepen with this example.
Since React renders data into DOM, you need to keep a variable that first has one value, and then another, so that the message is first shown and then hidden. You could remove the DOM element directly with jQuery's fadeOut, but manipulating DOM can cause problems.
So, the idea is, you have a certain property that can have one of two values. The closest implementation is a boolean. Since a message box is always in DOM, it's a child of some element. In React, an element is result of rendering a component, and so when you render a component, it can have as many children as you want. So you could add a message box to it.
Next, this component has to have a certain property that you can easily change and be completely sure that, as soon as you change it, the component gets re-rendered with new data. It's component state!
class App extends React.Component {
constructor() {
super();
this.state = {
showingAlert: false
};
}
handleClickShowAlert() {
this.setState({
showingAlert: true
});
setTimeout(() => {
this.setState({
showingAlert: false
});
}, 2000);
}
render() {
return (
<div>
<div className={`alert alert-success ${this.state.showingAlert ? 'alert-shown' : 'alert-hidden'}`}>
<strong>Success!</strong> Thank you for subscribing!
</div>
<button onClick={this.handleClickShowAlert.bind(this)}>
Show alert
</button>
(and other children)
</div>
);
}
}
Here, you can see that, for message box, either alert-shown or alert-hidden classname is set, depending on the value (truthiness) of showingAlert property of component state. You can then use transition CSS property to make hiding/showing appearance smooth.
So, instead of waiting for the user to click button to show the message box, you need to update component state on a certain event, obviously.
That should be good to start with. Next, try to play around with CSS transitions, display and height CSS properties of the message box, to see how it behaves and if the smooth transition happening in these cases.
Good luck!
PS. See a Codepen for that.
The correct way is to use Transition handler for Fade-in/out
In ReactJS there is synthetic event to wait till fade-out is finished: onTransitionEnd.
NOTE there are different css effects associated with different handlers. Fade is a Transition not an Animation effect.
Here is my example:
const Backdrop = () => {
const {isDropped, hideIt} = useContext(BackdropContext);
const [isShown, setState] = useState(true);
const removeItFromDOM = () => {
debugger
setState(false)
};
return isShown
? <div className={`modal-backdrop ${isDropped ? 'show' : ''} fade` } onClick={hideIt} onTransitionEnd={removeItFromDOM}/>
: null
}
An other way is to solve this with a CSS3 transition.
https://www.tutorialspoint.com/css/css_animation_fade_out.htm
You can add a new class to the alert (like .hidden) and then you can relate .hidden with the class you defined for the alert.
alert.hidden{
// Here you can define a css transition
}
In this solution you don't have to add a setInterval or anything, since css3 transitions already process it on browser render.
I have created an element with ReactJS and appended it to div container and then removed the same element using jQuery.remove().
Do I need to unmount the node that I created with React?
Any help will be appreciated!
Yes, you should do so. You can remove the element by
React.unmountComponentAtNode(document.getElementById('divsId'));
where the div you are removing the element from has to be the one you were originaly appending it to.
Interfering with the DOM causes unnecessary repainting and redrawing which are expensive processes. The best method is to set a flag like so:
getInitialState() {
isVisible: false
}
toggleVisiblity() {
var {isVisible} = this.state
this.setState({isVisible: !isVisible})
}
render() {
if(isVisible)
var display = <h2> Your content </h2>
return(
<button onClick={this.toggleVisibility}> Click here </button>
{display}
)
}
When isVisible is false, React automatically unmounts and when it is true, it mounts. This is a good practice to maybe even add animations.