Solve lifecycle difference measuring DOM nodes in React - reactjs

As I asked the other day to Dan Abramov on Twitter, I am trying to make some measuring of DOM nodes following this example in the official React docs.
Using a custom hook called useOffset, something like this:
function useOffset() {
const [offset, setOffset] = useState(null);
const ref = useCallback(node => {
if (node !== null) {
setOffset({
left: node.offsetLeft,
width: node.offsetWidth,
height: node.offsetHeight
});
}
}, []);
return [offset, ref];
}
And using in the desired Component like:
const [offset, ref] = useOffset();
But the problem I found is that the first render, the measurement is wrong due to not yet applied styles, FOUT and so on. The following renders will measure the right way.
Here is the sandbox with the reproduction.
The first time the page loads or clicking the reload button of the Codesandbox browser, the red outline won't match with the h1.title but hidding and showing the elements again through the button which fires a re-render changing the Component state, the outline match perfectly the size (offset) of the element measured.
In the same tweet is also a video showing the wrong and desired behaviour.
UPDATE seems to be happen only in Firefox browser.

After opened a react issue and thanks to the input and interest of #Kunkn and Brian Vaughn is confirmed that the issue is related to specific Firefox browser behaviour, not related to React.

Related

IntersectionObserver Flickering with ScrollIntoView

I'm trying to build a custom input that you can change its value by scrolling with IntersectionObserver and ScrollIntoView
The problem that I'm facing is that when I try to make the component controlled with a state it starts to flicker when scrolling.
I have the example here in this sandbox, and you can see the input gets initialized correctly with the correct value, but when you try to change it.. there is a flickering at the beginning of the scroll event. also resetting the input by the button does seem to work correctly.
I'm not really able to figure out how to get the updates correctly done in each event since I'm very new to Intersection observer
Try setting the threshold value to 1 such that it will fire only when it goes out of boundary completely.
const observer = new IntersectionObserver(
(entries) => {
const selectedEntry = entries.find(
(e) => Number.parseFloat(e.target.textContent) === value
);
selectedEntry?.target?.scrollIntoView();
entries.forEach((entry) => {
if (!entry.isIntersecting) {
return;
}
!isFirstRender &&
onChange(Number.parseFloat(entry.target.textContent));
});
},
{ threshold: 1 } // changed to 1
);
Also please do as the linter says, and add proper dependencies for the useEffect hook unless when not needed.
If you are using React, you might consider react-intersection-observer.
In my case, I was able to remove flickering by setting option triggerOnce: true.

Why does using React Context with Framer Motion not work?

I have a version of a slideshow where the state is being stored locally, you can see that the slideshow works great and the slide components only are unmounted once the animation is complete. https://stackblitz.com/edit/react-framer-motion-slideshow-official?file=src%2FSlideShow.js
Once I added the context to handle the values, the animation sliding still works but the exiting component is replaced with the new slide content when the animation begins, which looks really strange. Also the custom value for the slide directions seems to be broken. https://stackblitz.com/edit/react-framer-motion-slideshow-official-context?file=src%2FSlideShow.js
Do you have any ideas how I can get the animation to work correctly again when using context?
Everything that consumes a context re-renders every time that context’s state changes. So the children of your Slides component
rerender
see that the new variant = to the next state
appear at the destination
If I were you I wouldn't use context. If you really want to not explicitly pass the same props over and over you can do
{[
MainSettingsSlide,
ChangeLanguageSlide,
LanguageDetailsSlide,
BlockedSitesSlide
].map((Component, i) => (
<Component
activeSlideName={activeSlideName}
onNavigateSidebar={onNavigateSidebar}
key={i}
/>
))}
Sorry for the indirect answer :)
Edit two days later
In rereading your question I realize there are some other problems
You need to always conditionally render based on props not context
const Slide = ({ children, slideName, className, activeSlideName }) => {
// This context will update outside of framer-motion
// framer-motion animating something in while it is animating something out is
// predicated on you giving it control by using props
// const { activeSlideName } = useSlideShowContext();
// console.log('activeSlideName in Slide', activeSlideName);
// console.log('---------------------');
if (activeSlideName !== slideName) {
return null;
}
Your onNavigateSlideShow was using slideDirection instead of direction
const onNavigateSlideShow = ({ slide, direction = 'forward' }) => {
// const onNavigateSlideShow = ({ slide, slideDirection = 'forward' }) => {
console.log('ccc', direction);
setActiveSlide([slide, direction]);
};
I still can't get the directions to go in the right direction
I think this is due to a race condition between the direction being set and when the animation is kicked off
If you click the back button before the animation completes it works as expected.
Here is where I got to: https://stackblitz.com/edit/react-framer-motion-slideshow-official-context-dftoab?file=src/SlideShow.js
Sorry that this is again not a complete answer. I think I am coming to the same conclusion as before that the two apis probably shouldn't be mixed. Especially due to edge cases like this one.
There have been a decent number of questions recently about context and AnimatePresence so made sandbox for most of the cases that I could think of: https://codesandbox.io/s/framer-motion-using-context-with-animate-presensce-nuj0m

reactjs run code after all elements are loaded

I am in my first steps with react. I am running Reactjs v16.11.0
I have page which trigger the webcam (following this mdn tutorial). So I want to call startup function when all elements were painted. I tried with window.addEventListener('load', startup, false); but it doesn't call any function.
So I tried the useEffectHook:
useEffect (() => {
startup();
}, []);
But it call the startup function too soon, and there is some elments that there aren't still in the DOM because it runs asyncronous code - video .
My startup function is this
const startup= () => {
video = document.getElementById('video');
let canvas: HTMLCanvasElement = document.getElementById('canvas') as HTMLCanvasElement;
const photo = document.getElementById('photo') as HTMLElement;
navigator.mediaDevices.getUserMedia({video: true, audio: false})
.then(function(stream) {
mediaStream = stream.getTracks()[0];
video.srcObject = stream;
video.play();
})
.catch(function(err) {
console.log("An error occurred: " + err);
});
if(video) {
video.addEventListener('canplay', function (ev: any) {
if (!streaming) {
height = video.videoHeight / (video.videoWidth / width);
// Firefox currently has a bug where the height can't be read from
// the video, so we will make assumptions if this happens.
if (isNaN(height)) {
height = width / (4 / 3);
}
video.setAttribute('width', width);
video.setAttribute('height', height);
canvas.setAttribute('width', width.toString());
canvas.setAttribute('height', height.toString());
streaming = true;
}
}, false);
//clearphoto(canvas, photo);
}
}
I am using functional component (instead of class component). And from what I understood componentDidMount works with class component. Am I correct?
How can accomplish to run the startup function only when every elements are in the DOM ?
EDIT: code edit in useEffect hook, noticed by Jayraj
I have just finished following the tutorial. It was interesting to me, as well.
First of all, you can play my demo: https://codesandbox.io/s/hungry-bassi-ojccj?file=/src/App.js.
To achieve the goal I used three hooks: useRef, useEffect, useState. Let's me explain why I have used each of them.
So, I would like to start with the useState hook. Before streaming, we should calculate the height of the image and canvas and set it. However, we must save the height into somewhere to get its value in our component. That's why I used the useState hook.
To draw canvas successfully I used the useRef hook. It allows me to access the DOM and that's why I removed calls the getElementById from the code as the hook is responsible for. I made the same with the video. I created the videoRef to access the DOM.
And the main that I called the useEffect hook two times. As you can see, the first useEffect hasn't any dependencies that's why it will be called once. It works like the componentDidMount method in this case. Thankfully to it, the getUserMedia method is called and we can set stream to the videoRef and afterwards the video will be started playing.
The second useEffect waitings for the changes of the videoRef property and then it starts executing.
I guess you should read about react hook more deeply to understand very well. Let's me attach the link of the documentation. https://reactjs.org/docs/hooks-reference.html
Have a good day.

How to fix image flickering by rerendering once and for all?

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.

How to navigate to a particular div on the same page in ReactJS?

I have to navigate to a particular div on the same page in React JS application. For example how it is being done in the following website. When one clicks on < NavLink > the UI scrolls and focuses on the particular div and similarly for other Links on this website.
https://reacttraining.com/react-router/web/api/NavLink/strict-bool
Get the offset of the target element and use window.scrollTo to position it either on top of the page or where ever you want. This would work in chrome/latest browsers. You might want to check for other browser compatibility.
let offsetTop = document.getElementById("yourelement").offsetTop;
window.scrollTo({
top: offsetTop-100,
behavior: "smooth"
});
Program is in codepen https://codepen.io/Divine1/pen/zbQVwR
Update
i have added this logic into a online reactjs project editor and it works.
You can see how this code works https://repl.it/#Divine1/QuietHeartyApplicationstack
Check App.js App.css and the live result.
I think that maybe is a better aproach to use useRef hook. on click is almost the same:
const ref = useRef();
// the function when you want to scroll:
() => window.scrollTo({
top: ref.current.offsetTop -100,
behavior: "smooth"
})
// Where you want to go
<div ref={ref} />

Resources