IntersectionObserver Flickering with ScrollIntoView - reactjs

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.

Related

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

react-hook-form reset errors messages only

I have some dynamic fields, which gets removed/added on the basis of some hook state. I have fields which gets removed from the list but the errors for them are still visible. I have tried to clearErrors, unregister to remove it but nothing works.
is it possible? reset does work but it resets the whole form too.
I am using v6 of react-hook-form and i cannot upgrade it to 7. That's out of the picture for now.
yup validator is being used with it for validations.
I stuck into the same problem seems like bug, if you try unregister the control it doesn't do it. Here how I have done.
When you remove the control do unregister and reset specific control.
const handleRemoveRow = (control) => {
//all code logic and stuff
//................
unregister(control);
reset({ [control]: undefined });
};
After that on useEffect hook assume you have one main state of form, reassign the values back.
useEffect(() => {
const keyValue = getValues();
keyValues.map(({controlName,Value}) => {
setValue(controlName, Value);
});
}, [getValues()]);
This is a more of pseudo-code but I hope you got the concept.

useRef Did not change the element style in React

okay i am trying to make a toggle display element from hidden to block and vice versa and bind it to a button.
i use useRef and it somewhat work, but only one time, it doesn't toggle again after the element display is block.
here is the code i use.
const handleMobileMenu = () => {
setIsPressed(!isPressed);
const state = isPressed ? "flex" : "hidden";
mobileMenu.current.style.display = state;
console.log(state);
};
the console log displaying what i want which is block, hidden, block, hidden.. its toggling. but the element doesn't.
i've tried using, if else, but same problem.
yup silly me, just use a conditional className, and the problem is solved.
thanks Dennis Vash

Framer / React: Can't change focus state of Text Input onClick / onChangePage

This is a Framer/React question:
I have a bunch of text fields in a "page" component and I want the focus to shift on every "page" change.
There's a button controlling the pager but no trigger (onPageChange / onClick) is changing the attribute of "focus" on the relevant text input.
This is the code I tried using to change the value of foc2 (the focus attribute) to true
When I manually change it to true it works, but in this case it doesn't
export function Page(): Override {
return {
currentPage: state.page,
onChangePage: (index) => {
state.page = index
state.foc2 = true
},
}
}
Do you have more complete code you could share?
Make sure you set the state to the index of the current focused input, and use that to change the currentPage property.
Happy to help if you send more context, you can also find more help in Framer's community at https://framer.com/r/discord

How can I access React state in my eventHandler?

This is my state:
const [markers, setMarkers] = useState([])
I initialise a Leaflet map in a useEffect hook. It has a click eventHandler.
useEffect(() => {
map.current = Leaflet.map('mapid').setView([46.378333, 13.836667], 12)
.
.
.
map.current.on('click', onMapClick)
}, []
Inside that onMapClick I create a marker on the map and add it to the state:
const onMapClick = useCallback((event) => {
console.log('onMapClick markers', markers)
const marker = Leaflet.marker(event.latlng, {
draggable: true,
icon: Leaflet.divIcon({
html: markers.length + 1,
className: 'marker-text',
}),
}).addTo(map.current).on('move', onMarkerMove)
setMarkers((existingMarkers) => [ ...existingMarkers, marker])
}, [markers, onMarkerMove])
But I would also like to access the markers state here. But I can't read markers here. It's always the initial state. I tried to call onMapClick via a onClick handler of a button. There I can read markers. Why can't I read markers if the original event starts at the map? How can I read the state variables inside onMapClick?
Here is an example: https://codesandbox.io/s/jolly-mendel-r58zp?file=/src/map4.js
When you click in the map and have a look at the console you see that the markers array in onMapClick stays empty while it gets filled in the useEffect that listens for markers.
React state is asynchronous and it won't immediately guarantee you to give you the new state, as for your question Why can't I read markers if the original event starts at the map its an asynchronous nature and the fact that state values are used by functions based on their current closures and state updates will reflect in the next re-render by which the existing closures are not affected but new ones are created, this problem you wont face on class components as you have this instance in it, which has global scope.
As a developing a component , we should make sure the components are controlled from where you are invoking it, instead of function closures dealing with state , it will re-render every time state changes . Your solution is viable you should pass a value whatever event or action you pass to a function, when its required.
Edit:- its Simple just pass params or deps to useEffect and wrap your callback inside, for your case it would be
useEffect(() => {
map.current = Leaflet.map('mapid').setView([46.378333, 13.836667], 12)
.
.
.
map.current.on('click',()=> onMapClick(markers)) //pass latest change
}, [markers] // when your state changes it will call this again
for more info check this one out https://dmitripavlutin.com/react-hooks-stale-closures/ , it will help you for longer term !!!
Long one but you'll understand why this is happening and the better fixes. Closures are especially an issue (also hard to understand), mostly when we set click handlers which are dependent on the state, if the handler function with the new scope is not re-attached to the click event, then closures remain un-updated and hence the stale state remains in the click handler function.
If you understand it perfectly in your component, useCallback is returning a new reference to the updated function i.e onMapClick having your updated markers ( the state) in its scope, but since you are setting the 'click' handler only in the beginning when the component is mounted, the click handler remains un-updated since you've put a check if(! map.current), which prevents any new handler to be attached on the map.
// in sandbox map.js line 40
useEffect(() => {
// this is the issue, only true when component is initialized
if (! map.current) {
map.current = Leaflet.map("mapid4").setView([46.378333, 13.836667], 12);
Leaflet.tileLayer({ ....}).addTo(map.current);
// we must update this since onMapClick was updated
// but you're preventing this from happening using the if statement
map.current.on("click", onMapClick);
}
}, [onMapClick]);
Now I tried moving map.current.on("click", onMapClick); out of the if block, but there's an issue, Leaflets instead of replacing the click handler with the new function, it adds another event handler ( basically stacking event handlers ), so we must remove the old one before adding the new one, otherwise we will end up adding multiple handlers each time onMapClick is updated. For which we have the off() function.
Here's the updated code
// in sandbox map.js line 40
useEffect(() => {
// this is the issue, only true when component is initialized
if (!map.current) {
map.current = Leaflet.map("mapid4").setView([46.378333, 13.836667], 12);
Leaflet.tileLayer({ ....
}).addTo(map.current);
}
// remove out of the condition block
// remove any stale click handlers and add the updated onMapClick handler
map.current.off('click').on("click", onMapClick);
}, [onMapClick]);
This is the link to the updated sandbox which is working just fine.
Now there's another Idea to solve it without replacing click handler each time. i.e some globals, which I believe is not really too bad.
For this add globalMarkers outside but above your component and update it each time.
let updatedMarkers = [];
const Map4 = () => {
let map = useRef(null);
let path = useRef({});
updatedMarkers = markers; // update this variable each and every time with the new markers value
......
const onMapClick = useCallback((event) => {
console.log('onMapClick markers', markers)
const marker = Leaflet.marker(event.latlng, {
draggable: true,
icon: Leaflet.divIcon({
// use updatedMarkers here
html: updatedMarkers.length + 1,
className: 'marker-text',
}),
}).addTo(map.current).on('move', onMarkerMove)
setMarkers((existingMarkers) => [ ...existingMarkers, marker])
}, [markers, onMarkerMove])
.....
} // component end
And this one works perfectly too, Link to the sandbox with this code. This one works faster.
And lastly, the above solution of passing it as a param is okay too! I prefer the one with updated if block since it's easy to modify and you get the logic behind it.

Resources