react animation with gsap without react-transition-group and ref - reactjs

I have a question with GSAP and react, as I read from some tutorials, they all use react-transition-group and also many of them use ref as an alternative selector for GSAP how ever, if I use ref in my case, the whole page will be animated, I just want a single element to animate so I use id selector, and it works totally fine like this
import React from 'react';
import { TweenMax } from 'gsap';
import uuid from 'uuid';
import '../styles/homePage.css';
class HomePage extends React.Component{
startAnimation=(pic)=>{
TweenMax.from(`#${pic.id}`, 1, {
opacity: 0,
x: -100,
y: -100
});
}
render(){
const PicsNum = 15;
let pics = [];
let pic = {};
for (let i = 5; i <= PicsNum; i++) {
const picPath = `/pictures/testingPics/${i}.jpg`
pic={id:`a${uuid()}`, picPath}
pics.push(pic)
}
const renderPics = pics.map((p, i) => (
<div
key={i}
className='img-container'
>
<img src={p.picPath} className='pic' id={p.id}/>
<button onClick={()=>{this.startAnimation(p)}}>click</button>
</div>
))
return (
<div className='pics'>
{renderPics}
</div>
)
}
}
export default HomePage;
can someone please tell me why should I use react-transition-group and what can go wrong if I want to use animation without it like I am doing? thank you very much

So, what you are doing here is absolutely fine for simple animations. It's only when your logic and animations start becoming more complicated that you may find it has downsides.
The main problem you may encounter as the complexity of your logic / animation increases is that you actually are now using two different libraries to target the dom. React wants to be completely in control of the dom so it can do its thing. GSAP however also is now looking for an element in the dom and controlling it's state, and react doesn't know about so now things might get out of sync. React might re-render that component, resetting your opacity to 1, when the user has already triggered the fade out.
React-transition-group can be a useful tool in simplifying working with animating components in and out, but it is not the only way to do it or the be all and end all of react animation, so don't feel like you have to use it. Just maybe look into the ways in which is simplifies the code you have to write for every component you want to animate in or out. (It gives you specific lifestyles for animating in and out, and a callback to remove the component post animation, which is the bulk of the boilerplate for component transitions).
In the case of the first issue I mentioned Transition-group is useful here because all your animation code is wrapped within the helpers it provides, so react knows: 1)Your animating... don't do anything till you've finished... 2)now you've finished and I'm back in control.
But there are other options outside of transition group to deal with this dichotomy of dom control:
You can try to be super smart and declarative about it... use refs to access the elements and pass them to gsap animations that are triggered and controlled by state/props.
But there are brilliant libraries that will take all the hassle out of worrying about state and animation and things like https://github.com/azazdeaz/react-gsap-enhancer
This is a wonderful higher order component that just makes sure any changes that gsap makes to the elements are noticed and preserved across react re-rendering and state changes.
Honestly it's a bit magic, and makes working with react and GSAP an absolute pleasure.
Also to answer your question about 'Why refs' instead of the useful 'just pass a string of the ID to the gsap function':
There isn't a right in wrong here. A ref in react will store a pointer to that Dom element in memory. Making it a convenient lookup. Its main advantage is the reference to that element will not expire upon a react re-render. If you manually select an element using GetElementById, and that Dom node is replaced by a react re-render, then your variable reference will become undefined and you'll have to call GetElementById again. GetElementById is very cheap in performance terms, it's not about performance, just avoiding the boilerplate of having to 'find' a new reference to the Dom element after every re-render.

Related

Alternative to Reactdom.render and unmountComponentAtNode in react18

Important note:
I am aware of createRoot and root.unmount()! Unfortunately (If I understand this correctly) they should be used just once in the application for mounting the react application.
Problem description:
In our app we have a modal component that is rendered dynamically and added to the body of the html via ReactDOM.render(). When this modal is hidden, we unmountComponentAtNode().
Unfortunately, after upgrading to react18, unmountComponentAtNode becomes deprecated and the new unmount is (in my understanding) for the root only. The same problem is about if I try to modify the ReactDOM.Render() for createRoot. Then we would have 2 roots in the app which is wrong.
What is the proper way to attach the modal to the body element (next to root!) and unmount it after it should be destroyed? The implementation is a little bit "weird" (partially in jsx, partially not...) and I would like to avoid refactoring the whole component as there will be a lot of refactoring already in the code... So I would like to focus on refactoring this component (into jsx one) later. Now I have to figure out only the rendering / unmounting. I have been thinking about using Portals, but anyway I have to create that elements somehow and render them into the DOM where portals does not help me a lot.
Calling the createRoot and then render on the root in this modal component fires an error You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it. which is obvious. But there is no "useRoot()" hook or anything like that. Should I store the returned object (root) in some context or somewhere to use it later? Or what should be the best option to call the render? :/
I know how I should do that with classical functional component... But maybe there is some way that I can just refactor a piece of the code instead of the whole component and all its usecases. Maybe there is something I am not aware of (there is definitely thousands of things I am not aware of :D) that should simplify my life...
function modal() {
return (
<div>
...
</div>
)
}
Modal.show = () => {
modalEl = document.createElement('div');
util.destroy(el) => {
ReactDOM.unmountComponentAtNode(el);
el.remove();
}
const childs = props.childs;
REactDOM.render(childs, modalEl);
}
When I was thinking about portals, I thought I will just rewrite the last line of ReactDOM.render to portal like createPortal(childs, modalEl), unfortunately this does not render anything (except modalEl, but no childs inside). The childs are of type ReactNode (using typescript) and they are not empty (because of ReactDOM.render works without any problem).

How to lazy-load a React "widget"?

My terminology is probably wrong here, but I don't know what to call it other than a "widget" when you don't have a whole React app, but are attaching little React pieces to different roots on an otherwise static HTML page. But, that's what I'm doing:
const StripeForm = React.lazy(() => import('./Stripeform'));
// ...
const formPlaceholder = document.getElementById('cwr-stripe-form');
const formRoot = createRoot(formPlaceholder);
formRoot.render(
<React.StrictMode>
<StripeForm />
</React.StrictMode>
);
The problem I'm trying to solve is, I want to lazy-load my StripeForm, so that it doesn't load until needed (and therefor maybe never at all).
I have the React.lazy import and my webpack bundle working fine. The issue is "until needed".
As I understand it, lazy-loaded components load when they begin rendering, which I suppose is usually when they move into the viewport. And I equally suppose that "usually" is overridden by the fact that I'm calling .render, which I guess causes it to render immediately.
Those guesses might be wrong, but the fact is, when I load my web page, the supposedly lazy-loaded component is loaded, even if it's not in the viewport.
How to I get these sort of "widgets" (there are several others on the page) to load lazily (i.e., to attach to my root placeholder element, but not actually load and render until necessary)?
You're already using lazy, so React will only import the component if it's not being rendered. The problem is that you're still rendering the component by default, so the component is still being loaded once it's available.
React is declarative, so the way to solve this is to conditionally render the component only when you want it to be rendered. You can implement this by using a visibility library such as react-is-visible, for example:
import React, { useRef } from 'react'
import { useIsVisible } from 'react-is-visible'
function LazyStripeForm() {
const ref = useRef()
const isVisible = useIsVisible(ref, { once: true })
return <div ref={ref}>{isVisible && <StripeForm />}</div>
}
Now you can render LazyStripeForm instead of StripeForm and it should do what you want.
Also, if StripeForm has a lot of dependencies, you should ensure your build tool is code splitting the file so that it's not increasing the size of your main bundle unnecessarily.

I want to use transition effects in react app. What type/ library for animation in react app should I use according to the latest trend?

I want to use some transition effects in my react js app. I am using function components in my app.
How do I include transition effects in app according to the business requirement these days?
I want to use animation such that on every render I can see the effect. It would be great if someone can help me out with an example.
If you want to use a library, I would suggest react-spring
https://react-spring.io/ it is based on spring physics, If you want to read about that more check this out https://www.joshwcomeau.com/animation/a-friendly-introduction-to-spring-physics/
And there is also another good option which is framer motion https://www.framer.com/motion/ which apparently offers more possibilities maybe out of the box (I personally have never tried it before)
For examples you can check their websites they have good examples.
I'm not sure what effect you are trying to generate.
css can be used by itself to generate animations or transitions.
You want to see the effect on each render?
i.e. You want to tie the effect to the react render cycle?
non-memoized values will change on every render
You could use a simple statement like const trigger = {};
Then react to trigger with a useEffect
useEffect(() => { do something }, [trigger]);
finally, visual effect.. apply a class based on state and use setTimeout to remove the state (and therefore the class)
This could be overly involved for exactly what you are trying to achieve but this works for all possible flows based on the question.
Here is one example with div element is moving to according vertical scroll position .
Look carefully.
First, Set the position using useState and define the window.onscroll function.
const [cardTop, setCardTop] = useState(0);
window.onscroll = function() {
if (window.pageYOffset < 30) {
setCardTop(window.pageYOffset + 'px');
}
};
Second, Set the style's top as state variable.
<div className='card t-card' id='tCard' style={{top:`${cardTop}`}}> ...
Congratulations. It probably act exactly.
It's similar to use Jquery or another Javascript, Only use state variable.
Thanks.

Ag grid using framework cell renderer keeps re-rendering on any store change

Trying to implement a custom cell framework renderer with React and it seems simple enough. Create the React component, register it with frameworkComponents.
The data that populates rowData is coming from my redux store. The custom cell renderer is a functional react component.
The issue is that because I'm using a frameworkComponent - a React component in my case - as a cellRenderer, it seems that any change in the data for the grid the I'm getting via useSelector(selectorForMyData) causes a re-render of my frameworkComponent, which on the browser, looks like a random, annoying flicker. The application is heavily wired into redux
Two questions:
1 - How come when I natively use ag grid to render this cell using a AgGridColumn without any custom cell renderers, it doesn't cause this re-rendering behavior on the same store changes? I have a click event bound to the main page that toggles a flag to false (in the case a snackbar alert was open).
2 - Is there any way to get around this? I've tried wrapping my return statement in the framework cell renderer component with a useMemo with the params as a dependency, but that doesn't seem to work. Also tried making a render function via useCallback with the same idea as useMemo and that doesn't help either :/
Thanks
pseudo-code for situation:
App.tsx:
<MyAgGrid/>
MyAgrid.tsx:
const MyAgGrid = () => {
const data = useSelector(selectorForData);
return (
<AgGridReact
rowData={data}
frameworkComponents={
{'myCustomRenderer': CustomRendererComponent}
}
columnDefs={
['field': 'aField', cellRenderer: 'myCustomRenderer']
} />
);
};
CustomCellRendererComponent.tsx:
const CustomCellRendererComponent = (params) => {
console.log("params", params) //logs on every redux store update
return (
<div>HELLO WORLD</div>
);
};
The cells that are rendered via the CustomCellRendererComponent are re-rendered on any redux store change. I'm guessing it's due to useSelector causing the re-render on store changes, which is expected, but then it doesn't answer my first question.
EDIT:
I went "function as class" route shown here ("MyCellRenderer") and so far am not seeing the re-rendering issue, so I will stick with that for now even though it's god ugly. This leads me to believe my issue is trying to fit a React component/hooks, with its lifecycle nuances, as a cell renderer is causing problems. Still, I feel like there should be a way to prevent the behavior of constant re-rendering, otherwise it's a pretty useless feature
EDIT pt 2:
I dug deeper and while I haven't found an out of the box solution for it, I added the reselect library to memoize some of my selectors. The selector I use to get rowData is now memoized, and I'm no longer seeing this issue. Will mark as answer in a few days if no one provides a better, ideally out of the box (with redux or ag grid), solution for it.
As I stated in one of my edits. I figured it out, kind of.
I added the library reselect to the application, and it fixes the symptoms and I'm content with it going forward. It allows you to memoize your selectors, so that it only registers changes/"fires" (leading to a re-render) only if the any of the dependency selectors you hook it into changes/fires, so now, my grid doesn't "flicker" until the actual row data changes since my selectorForRowData is memoized!
I'm still uncertain why prior to using a frameworkComponent (React Component) as a cell renderer I wasn't seeing the symptoms, but I'm happy to just assume Ag-Grid has a lot of performance tricks that clients may lose when plugging in their own custom functionality, as is the case in a custom cell renderer.

React.forwardRef is already possible without it, so what's the use of it?

I'm confused on the point of React.forwardRef. As explained in its documentation, I understand that its main use is for a Parent Component to gain access to DOM elements of the Child Component. But I can already do that without even having to use it.
Here is a code example that you can plug into CodeSandbox and see that it works:
import React, {useRef, useEffect} from "react";
import "./styles.css";
const ChildComponent = (props) => {
useEffect( ()=> {
props.callbackFunction()
})
return(
<div ref={props.fRef}>
{"hello"}
</div>
)
}
export default function App() {
const callbackFunction = () => {
console.log("The parent is now holding the forwarded ref to the child div: ")
console.log(forwardedRef)
}
const forwardedRef = useRef(null)
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<ChildComponent name="gravy" callbackFunction={callbackFunction} fRef={forwardedRef}/>
</div>
);
}
Or here's the embed of this example. Honestly, I'm kind of new to this and I don't know exactly how embeds work and whether someone fiddling with the embed changes my original Sandbox or not, so I was hesitant to put it. But here it is.
Example Forwarding Ref
In the example, the parent App() component successfully passes a ref to the child which the child attaches to its rendered div. After it renders, it calls a callback function to the parent. The parent then does a console log where it proves that its forwarded ref now has a hold of the child's div. And this is all done without React.forwardRef.
So what then is the use for React.forwardRef?
You're absolutely right that you can do what you've described. The downside is that you're forced to expose an API (ie: the fRef prop) for it to work. Not a huge deal if you're a solo developer building an app, but it can be more problematic eg. if you're maintaining an open-source library with a public API.
In that case, consumers of the library won't have access to the internals of a component, meaning you'd have to expose it for them somehow. You could simply do what you're suggesting in your example and add a named prop. In fact, that's what libraries did before React 16.3. Not a huge deal, but you'd have to document it so people know how to use it. Ideally, you'd also want some kind of standard that everyone used so it wasn't confusing (many libraries used the innerRef naming convention), but there'd have to be some consensus around that. So all doable, but perhaps not the ideal solution.
Using forwardRef, passing a ref to a component just works as expected. The ref prop is already standardized in React, so you don't need to go look at docs to figure out how to pass the ref down or how it works. However, the approach you describe is totally fine and if it meets your needs, by all means go with that.
As mentioned in the docs , it's useful for highly reusable components, meaning components that tend to be used like regular HTML DOM elements.
This is useful for component libraries where you have lots of "leaf" components. You've probably used one like Material UI.
Example:
Let's say you're maintaining a component library.
You create a <Button/> and <Input/> component that maybe just adds some default styling.
Notice how these components literally are just like regular HTML DOM elements with extra steps.
If these components were made to be used like regular HTML DOM elements, then I expect all the props to be the same, including ref, no?
Wouldn't it be tedious if to get the button ref from your <Button/> component I'd have to get it through something like fRef or buttonRef ?
Same with your <Input/>, do I have to go to the documentation just to find out what ref to use and it's something like inputRef ? Now I have to memorize?
Getting the ref should be as simple as <Button ref={}/>
Problem
As you might know, ref will not get passed through props because, like key, it is handled differently by React.
Solution
React.forwardRef() solves this so I can use <Button ref={}/> or <Input ref={}/>.

Resources