I'm trying to animate a component entering and leaving the dom, but I also want to animate it being replaced with another instance of itself. I tried everything on react-transition-group and nothing works as I expect.
There is Transition group, which is able to handle elements leaving the dom and getting fully empty, but it is not capable of waiting for one element to leave before animating the next.
I also tried SwitchTransition, but that one is not able to accept empty children, you need to always provide at least one child, which will be animated, so I can not just put a random empty div there.
I suspect that I can achieve this playing with complete callbacks, but I am unsure about how.
Related
I am trying to use a parent component to control animations in a child Canvas element. Specifically I want an animation to happen when a user inputs a correct answer.
It works until the user changes the state in the parent component, and then it no longer works.
I've made a stripped-back, minimal version of my code here to show my issue: https://codesandbox.io/s/epic-leaf-08jqvy?file=/src/App.js
My desired behaviour is that the red box bounces when a user clicks submit. That happens if they don't type anything in the input box, but as soon as you enter anything into there - changing state and re-rendering the component - the button no longer triggers the animation in the Canvas child component.
As far as I can tell, the issue is to do with changing the state when inputing text. If I make a version where the input is just assigned to a variable, it works fine, but I need to be able to use state and re-render other parts of it.
I have put a console.log in the jump() function, so I can see that it is being called, but no animation is taking place in the canvas.
I assume that what's happening is that everything is being re-rendered when the state changes, and so the useRef is no longer tracking to the right thing.
Things I've tried:
putting the canvas in a memoized component to prevent it from re-rendering
using eventlisteners to see if I can trigger the animations in other ways - keydown ones work, but I need the user to be able to type, so I tried other ones (like hashchange or audio.play) but neither of those worked.
You can see the thing I'm actually trying to build here: https://papaya-platypus-86565f.netlify.app/play Basically users answer questions and an animation plays depending on whether they're right or wrong, to give it a game-y feel.
Thanks!
I like your red box as well as your reasoning. Yes, the input state changing on keystroke is causing the entire App component to re-render. Note that your App.js component has a lot going on (all good stuff), such as your Box class instantiation, your canvas instantiation, etc.
The key is to break your components into smaller parts, particularly separating stateful components from non-stateful components. We don't want your canvas re-mounting on every input change, so we make them sibling components!
Here's a working example of your code, just in smaller components:
https://codesandbox.io/s/strange-julien-d3n4zm
I hope this helps.
I have implemented code in focusGained that in some cases may cause the component (a container I have set to be focusable) which just received that focus to be removed from the screen, and replaced in the container by another component. This will also cause a redraw of the screen to show the changed component.
This does work but I have a bit of a problem, in that the next component in the container also gains focus and so my code removes/replaces it also. I assume this is because on the redraw, that component now occupies the same space where the tap originally occurred.
If the last component in the container was originally selected, then the replacement component is itself given focus and so it is removed and replaced.
Any ideas on what I may have miscoded or anything I can do to avoid the second focusGained call?
I doubt you did something wrong. If we remove the component with the focus we'll find the next available one. The replace method doesn't take focus into consideration but can't grant focus to a component that isn't physically here yet so it's granted to the next component. Not much to do here.
You can requstFocus() after the transition completes to fix the order manually.
I'm trying to get an position:absolute arrow that is pointing to a targetelement to reposition whenever something in my layout changes, if that affects the position of the targetelement (e.g. sidebar opens or closes or window gets resized).
Something along the lines of whenever the boundingClientrect of the targetelement changes. Adding elemRect.left etc to the dependency array doesn't work as I intend it to unfortunately. In that case it updates the arrow only once when the state changes, instead of following the animation of the target smoothly.
I've tried a ResizeObserver as well which doesn't fire whenever the target element moves but only when it resizes.
Omitting the dependency array (like in the example) achieves what I want to happen but it completely fries the computer. I also frequently get a "Maximum updated depth exceeded error" which makes sense since the arrow updates the state basically all the time.
Using useLayoutEffect doesn't work either.
I have a small example of what I need here:
https://codesandbox.io/embed/suspicious-haze-4jdi1
I'd gladly take advice on how to tackle this problem properly.
Thanks in advance!
This Is more of a "theoretical" question that often buffles me in different situations and use cases, I will give a simple example to demonstrate it.
Let's say I have a list of 10 buttons.
Everrytime I click a button, a floating menu appears on top of the clicked button - there is only one menu visible for any given time.
Let's assume that I can't render this floating menu within the button component and I can only render it in the buttons parent level (meaning that this menu is sibling to those buttons).
I have 2 possible options to do that:
Keep the x,y position of the last clicked button and render the menu in this given position
Render the menu once and using "ref" to directly relocate the menu
On the one hand, the first approach seems more "Reactish". On the other hand, the possible implemention I can think of is pretty ugly (capturing the clicked item position and saving it to state which triggers defender), and further more, I am not so sure about re re rendering the whole container just because I need to move a small piece of it.
The second approach touches the DOM directly using refs. Although possible , doing DOM manipulations sometimes feel bad to me.
Is there a better approach? Which of the 2 makes more sense?
Any suggestion or thoughts will be appreciated!
Thanks
React uses whats called a virtual DOM, which is a representation of the DOM, that sits on top of the real browser DOM. Whenever you update state or a user performs an action the virtual DOM compares and checks the changes with the real DOM and then updates the UI accordingly.
So if certain DOM elements like a are not different between changes it does not get re rendered, only the DOM elements that have changed are re rendered. And if a property on a DOM element is changed, only the property is updated and the DOM element is not re rendered.
<div color="blue" />
to
<div color="red" />
The whole element is not destroyed and re created, only the property is changed.
However if the element in the host tree is different than the entire host tree is destroyed and recreated.
<div />
to
<p>
This is refereed to as reconciliation
https://reactjs.org/docs/reconciliation.html
So using refs is definitely more of a hacky solution since its more of an escape hatch and directly manipulates the DOM.
I would definitely stick with option 1, I think there is an elegant solution to the use case you described, it would involve just adding a click event listener in the componentDidMount and keeping track of the click position that way.
And also its hard to say without code but since your buttons will be the same, they will not be re rendered only the menu will.
Would recommend for further reading
https://overreacted.io/react-as-a-ui-runtime/
I used react-infinite-scroll-component it's working just fine.
However, I want to avoid making the user lose his scroll position when he leaves the page and clicks back?
Also please put on consideration Firefox and Safari.
Not 100% sure because I haven't used it - but since no one else has chimed in... the docs say the component has a prop named key that is described as:
the key for the current data set being shown, used when the same
component can show different data sets at different times,
default=undefined
Also, it has a prop named onScroll that is described as:
a function that will listen to the scroll event on the scrolling
container. Note that the scroll event is throttled, so you may not
receive as many events as you would expect.
... which I suspect one of the arguments of which will tell you which keys it loaded / scrolled through.
So my approach would be to use componentWillUnmount to save the last key it loaded into a parent property (or Redux store if you're using Redux)... and when the component loads, if the key exists in the parent (or Redux store if you're using Redux) then pass it that key.