How to lazy-load a React "widget"? - reactjs

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.

Related

1 page application with next.js

i am bulding a project thats have only 1 page without routing, now i want to use getStaticProps.
so the only way, I can use getStaticProps its in my pages/index.tsx and its kinda annoying because the index.tsx getting bigger and bigger, theres any method to prevent that? and still have a single page project?
I tried to create new components like - pages/options
and use getStaticProps there and render the pages/options in pages/index,
but the data doesn't shown and i don't think its a good practice.
For the markup, I would recommend breaking the page down into display components (components that only render data) and passing them props from the page. To make your file cleaner, you could move the getStaticProps function to another file and then import it into pages/index.tsx like so -
import { fetchProps } from "../lib/fetchProps";
// ...
export const getStaticProps = fetchProps;

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).

Does it actually matter if you import packages in first component mount?

I'm curious if the two code snippets below actually matter in terms of performance. On the first one, we have our good old way to import packages. Just import 'em directly at the top level of the code. On the second one, I'm setting the state once the import statement finishes inside React.useEffect()
import React from "react";
import { Link } from "gatsby";
export default function SomeFunc() {
....
}
import React from "react";
export default function SomeFunc() {
const [link, setLink] = useState();
React.useEffect(() => {
import("gatsby").then(({ Link }) => setLink(Link));
}, []);
}
In the described case, it will. Since you are blocking the <Link> component usage until the package is loaded, even more, if you are waiting for the DOM tree (because of the useEffect with empty deps([])). With the native approach (the first one), you are bundling the package at the build-time so it will have better performance overall since it's a dependency used by Gatsby itself.
There's a big however, the dynamic imports add the huge benefit of reducing the bundle size, the payload response, especially in SPA applications, but their usage must be coherent to the package lazy-loaded.
I would recommend https://blog.mgechev.com/2019/05/11/dynamic-imports-javascript/.

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={}/>.

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

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.

Resources